int i = 0, y = 0; int uvp = 0, u = 0, v = 0; int y1192 = 0, r = 0, g = 0, b = 0;
for (int j = 0, yp = 0; j < height; j++) { uvp = frameSize + (j >> 1) * width; u = 0; v = 0; for (i = 0; i < width; i++, yp++) { y = (0xff & ((int) yuv420sp[yp])) - 16; if (y < 0) y = 0; if ((i & 1) == 0) { v = (0xff & yuv420sp[uvp++]) - 128; u = (0xff & yuv420sp[uvp++]) - 128; }
y1192 = 1192 * y; r = (y1192 + 1634 * v); g = (y1192 - 833 * v - 400 * u); b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143; if (g < 0) g = 0; else if (g > 262143) g = 262143; if (b < 0) b = 0; else if (b > 262143) b = 262143;
/** 现在的编码出来的颜色是反的 如 蓝色成了橙色,黄的是红的 * 尝试 b g r 顺序 */ } }//for return rgbBuf; }// decodeYUV420Sp2RGB
public static byte[] decodeYUV420SP2YUV420(byte[]data,int length) { int width = 176; int height = 144; byte[] str = new byte[length]; System.arraycopy(data, 0, str, 0,width*height); int strIndex = width*height; for(int i = width*height+1; i < length ;i+=2) { str[strIndex++] = data[i]; } for(int i = width*height;i<length;i+=2) { str[strIndex++] = data[i]; } return str; } //YUV420SP2YUV420
public static byte[] encode(final byte[] current, final byte[] previous, final int blockWidth, final int blockHeight, final int width, final int height) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024); if (previous == null) { baos.write(getTag(0x01 /* key-frame */, 0x03 /* ScreenVideo codec */)); } else { baos.write(getTag(0x02 /* inter-frame */, 0x03 /* ScreenVideo codec */)); } // write header final int wh = width + ((blockWidth / 16 - 1) << 12); final int hh = height + ((blockHeight / 16 - 1) << 12); writeShort(baos, wh); writeShort(baos, hh); // write content int y0 = height; int x0 = 0; int bwidth = blockWidth; int bheight = blockHeight; while (y0 > 0) { bheight = Math.min(y0, blockHeight); y0 -= bheight; bwidth = blockWidth; x0 = 0; while (x0 < width) { bwidth = (x0 + blockWidth > width) ? width - x0 : blockWidth; final boolean changed = isChanged(current, previous, x0, y0, bwidth, bheight, width, height); if (changed) { ByteArrayOutputStream blaos = new ByteArrayOutputStream(4 * 1024); DeflaterOutputStream dos = new DeflaterOutputStream(blaos, deflater); for (int y = 0; y < bheight; y++) { //Log.i("DEBUG", "current的长度:"+current.length+" 起始点:"+3 * ((y0 + bheight - y - 1) * width + x0)+" 终点:"+3 * bwidth); dos.write(current, 3 * ((y0 + bheight - y - 1) * width + x0), 3 * bwidth); } // dos.write(current, 0, current.length); dos.finish(); deflater.reset(); final byte[] bbuf = blaos.toByteArray(); final int written = bbuf.length; // write DataSize writeShort(baos, written); // write Data baos.write(bbuf, 0, written); } else { // write DataSize writeShort(baos, 0); } x0 += bwidth; } } return baos.toByteArray(); }
/** * Writes short value to the {@link OutputStream <tt>os</tt>}. * * @param os * @param n * @throws Exception if an exception occurred */ private static void writeShort(OutputStream os, final int n) throws Exception { os.write((n >> 8) & 0xFF); os.write((n >> 0) & 0xFF); }
/** * Checks if image block is changed. * * @param current * @param previous * @param x0 * @param y0 * @param blockWidth * @param blockHeight * @param width * @param height * @return <code>true</code> if changed, otherwise <code>false</code> */ public static boolean isChanged(final byte[] current, final byte[] previous, final int x0, final int y0, final int blockWidth, final int blockHeight, final int width, final int height) { if (previous == null) return true; for (int y = y0, ny = y0 + blockHeight; y < ny; y++) { final int foff = 3 * (x0 + width * y); final int poff = 3 * (x0 + width * y); for (int i = 0, ni = 3 * blockWidth; i < ni; i++) { if (current[foff + i] != previous[poff + i]) return true; } } return false; }
/** * @param frame * @param codec * @return tag */ public static int getTag(final int frame, final int codec) { return ((frame & 0x0F) << 4) + ((codec & 0x0F) << 0); } }
public void onClick(View v) { try{ if (v == startButton) { this.setTitle("开始录音了!");
if(recorderInstance == null){ System.out.println("main"); recorderInstance = new PcmRecorder(); // 实例化PcmRecord 编码和获取声音源数据 System.out.println("main 2"); Thread th = new Thread(recorderInstance); System.out.println("main 3"); th.start(); System.out.println("main 4"); } recorderInstance.setRecording(true); } if (v == stopButton) { this.setTitle("停止了"); recorderInstance.setRecording(false); } if (v == exitButon) { recorderInstance.setRecording(false); System.exit(0); } }catch(Exception e ){ System.out.println("msg: "+e); } } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); startButton = new Button(this); stopButton = new Button(this); exitButon = new Button(this); textView = new TextView(this);
LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.addView(textView); layout.addView(startButton); layout.addView(stopButton); layout.addView(exitButon); this.setContentView(layout); } }PcmRecord类: 该类进行编码和声音来源获取package com.ryong21.io;import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder;import com.ryong21.encode.Encoder;public class PcmRecorder implements Runnable { private volatile boolean isRecording; private final Object mutex = new Object(); private static final int frequency = 8000; private static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; public PcmRecorder() { super(); System.out.println("rec super"); } public void run() { System.out.println("rec run"); // 启动编码线程 Encoder encoder = new Encoder(); // 实例化编码类 该类引用了 speex 外部编码链接 Thread encodeThread = new Thread(encoder); encoder.setRecording(true); encodeThread.start(); System.out.println("rec encodestart"); synchronized (mutex) { System.out.println("rec mutex "+this.isRecording); while (!this.isRecording) { try { mutex.wait(); } catch (InterruptedException e) { System.out.println("rec Interrupted "+e); throw new IllegalStateException("Wait() interrupted!", e); } } } System.out.println("rec mutexed"); android.os.Process .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); int bufferRead = 0; int bufferSize = AudioRecord.getMinBufferSize(frequency, AudioFormat.CHANNEL_IN_MONO, audioEncoding); System.out.println("rec buffersize"); short[] tempBuffer = new short[bufferSize]; /** 获取声音源 **/ AudioRecord recordInstance = new AudioRecord( MediaRecorder.AudioSource.MIC, frequency, AudioFormat.CHANNEL_IN_MONO, audioEncoding, bufferSize); System.out.println("rec startrec"); recordInstance.startRecording(); System.out.println("while "+isRecording); while (this.isRecording) { bufferRead = recordInstance.read(tempBuffer, 0, bufferSize); System.out.println("------------------ buffered -------------\n"+bufferSize+"\n"+bufferRead+"\n-----------------------------------\n"); if (bufferRead == AudioRecord.ERROR_INVALID_OPERATION) {System.out.println("returned AudioRecord.ERROR_INVALID_OPERATION "); throw new IllegalStateException( "read() returned AudioRecord.ERROR_INVALID_OPERATION"); } else if (bufferRead == AudioRecord.ERROR_BAD_VALUE) {System.out.println("returned AudioRecord.ERROR_BAD_VALUE"); throw new IllegalStateException( "read() returned AudioRecord.ERROR_BAD_VALUE"); } else if (bufferRead == AudioRecord.ERROR_INVALID_OPERATION) {System.out.println("returned AudioRecord.ERROR_INVALID_OPERATION"); throw new IllegalStateException( "read() returned AudioRecord.ERROR_INVALID_OPERATION"); } System.out.println("encoder isIdle :"+encoder.isIdle()); if (encoder.isIdle()) { encoder.putData(System.currentTimeMillis(), tempBuffer, bufferRead); } else { // 认为编码处理不过来,直接丢掉这次读到的数据 System.out.println("认为编码处理不过来,直接丢掉这次读到的数据"); } } System.out.println("rec stop"); recordInstance.stop(); encoder.setRecording(false); } public void setRecording(boolean isRecording) { synchronized (mutex) { this.isRecording = isRecording; if (this.isRecording) { mutex.notify(); } } } public boolean isRecording() { synchronized (mutex) { return isRecording; } } }Encode类: 对音频进行编码类package com.ryong21.encode;import com.ryong21.io.Consumer; import com.ryong21.io.net.Publisher;public class Encoder implements Runnable { private volatile int leftSize = 0; private final Object mutex = new Object(); private Speex speex = new Speex(); private int frameSize; private long ts; private byte[] processedData = new byte[1024]; private short[] rawdata = new short[1024]; private volatile boolean isRecording; public Encoder() { super(); System.out.println("encode supered"); speex.init(); System.out.println("encode init"); frameSize = speex.getFrameSize(); //引用 .so 动态链接库 System.out.println("encode framesize: "+frameSize); } public void run() { System.out.println("encode run"); // 启动publisher线程写发布流媒体。 Consumer consumer = new Publisher(); // 启动publisher 流对音频数据进行上传 引用了 publishClient 类 System.out.println("encode consumer new Publisher"); Thread consumerThread = new Thread((Runnable) consumer); System.out.println("encode thread consumerThread"); consumer.setRecording(true); System.out.println("encode consumerThread start"); consumerThread.start(); System.out.println("encode consumer start"); android.os.Process .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); int getSize = 0; while (this.isRecording()) { synchronized (mutex) { while (isIdle()) { try { mutex.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } synchronized (mutex) { getSize = speex.encode(rawdata, 0, processedData, leftSize); System.out.println("encode getSize: "+getSize); setIdle(); }
// 将编码后的数据传给publisher线程。 if (getSize > 0) { consumer.putData(ts, processedData, getSize); } } consumer.setRecording(false); } public void putData(long ts, short[] data, int size) { synchronized (mutex) { this.ts = ts; System.arraycopy(data, 0, rawdata, 0, size); this.leftSize = size; mutex.notify(); } } public boolean isIdle() { return leftSize == 0 ? true : false; } public void setIdle() { leftSize = 0; } public void setRecording(boolean isRecording) { synchronized (mutex) { this.isRecording = isRecording; if (this.isRecording) { mutex.notify(); } } } public boolean isRecording() { synchronized (mutex) { return isRecording; } } } speex 类 package com.ryong21.encode; class Speex { /* quality * 1 : 4kbps (very noticeable artifacts, usually intelligible) * 2 : 6kbps (very noticeable artifacts, good intelligibility) * 4 : 8kbps (noticeable artifacts sometimes) * 6 : 11kpbs (artifacts usually only noticeable with headphones) * 8 : 15kbps (artifacts not usually noticeable) */ private static final int DEFAULT_COMPRESSION = 8; Speex() { } public void init() { load(); open(DEFAULT_COMPRESSION);
}
private void load() { try { System.loadLibrary("speex"); } catch (Throwable e) { e.printStackTrace(); } } public native int open(int compression); public native int getFrameSize(); public native int decode(byte encoded[], short lin[], int size); public native int encode(short lin[], int offset, byte encoded[], int size); public native void close();
首界面
package com.example.android_red5_t2;
import com.example.RTMPConnectionUtil.RTMPConnection;
import com.example.RTMPConnectionUtil.UltraNetStream;
import com.example.RTMPDecordUtil.RemoteUtil;
import com.example.android_red5_t2.util.SystemUiHider;
import com.smaxe.io.ByteArray;
import com.smaxe.uv.client.IMicrophone;
import com.smaxe.uv.client.INetStream;
import com.smaxe.uv.client.NetStream;
import com.smaxe.uv.client.camera.AbstractCamera;
import com.smaxe.uv.stream.support.MediaDataByteArray;public class FullscreenActivity extends Activity {
private static final boolean AUTO_HIDE = true;
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
private static final boolean TOGGLE_ON_CLICK = true;
private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
private SystemUiHider mSystemUiHider;
private int cameraCount = 0;
private int num = 1;
private Button overit;
private Button startit;
private Button changeit;
public static AndroidCamera aCamera;
private boolean streaming;
private boolean isTiming = false;
private Parameters parameters;
private Size pictureSize;
private Size previewSize;
private String version;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
overit = (Button)findViewById(R.id.overit);
mSystemUiHider = SystemUiHider.getInstance(this, contentView,
HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
int mControlsHeight;
int mShortAnimTime;
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
if (mControlsHeight == 0) {
mControlsHeight = controlsView.getHeight();
}
if (mShortAnimTime == 0) {
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
controlsView
.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
} else {
controlsView.setVisibility(visible ? View.VISIBLE
: View.GONE);
} if (visible && AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
}
});
contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (TOGGLE_ON_CLICK) {
mSystemUiHider.toggle();
} else {
mSystemUiHider.show();
}
}
});
overit.setOnTouchListener(
mDelayHideTouchListener);
overit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (RTMPConnection.netStream != null) {
RTMPConnection.netStream.close();
RTMPConnection.netStream = null;
}
if (aCamera != null) {
aCamera = null; }
finish();
}
});
initCamera();
}
private void initCamera(){
version = android.os.Build.VERSION.RELEASE;
aCamera = new AndroidCamera(FullscreenActivity.this);
startit = (Button)findViewById(R.id.beginit);
changeit = (Button)findViewById(R.id.change);
startit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//publish
new Thread(){
public void run(){
try{
aCamera.startVideo();
}catch(Exception e){
System.out.println("error : "+e.getMessage());
}
}
}.start();
}
});
changeit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
if(cameraCount != 1){
switch (num%2) {
case 0:
num++;
openCamert(0);
break;
case 1:
num++;
openCamert(1);
break;
}
}
}
});
}
@SuppressLint("NewApi")
private void openCamert(int i){
aCamera.camera.stopPreview();
aCamera.camera.release();
aCamera.camera = null;
aCamera.camera = Camera.open(i);
try{
aCamera.camera.setPreviewDisplay(aCamera.surfaceHolder);
aCamera.camera.startPreview();
}catch(IOException e){
}
}
/**
* ICamera
* @author Administrator
*
*/
public class AndroidCamera extends AbstractCamera implements SurfaceHolder.Callback,Camera.PreviewCallback{
// 麦克风
private MediaRecorder mediaRecorder = new MediaRecorder();
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
public Camera camera;
private int width;
private int height;
private boolean init;
int blockWidth;
int blockHeight;
int timeBetweenFrames; // 1000 frameRate
int frameCounter;
byte[] previous;
private Context context;
// 得到摄像头的个数 前置后置
private int cameraPosition = 0; // 0 前置 1 后置
@SuppressLint("NewApi")
public AndroidCamera(Context context){
this.context = context;
surfaceView = (SurfaceView)findViewById(R.id.videoSurfaceView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(AndroidCamera.this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
width = 352;
height = 288;
init = false;
cameraCount = Camera.getNumberOfCameras();
}
private void startVideo(){
String roomId = "androidRoom1";
RTMPConnection.connect_CreateRoom(roomId);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if(!init){
blockWidth = 32;
blockHeight = 32;
timeBetweenFrames = 100;
frameCounter = 0;
previous = null;
init = true;
}
final long ctime = System.currentTimeMillis();
/** 将采集的YUV420SP数据转换为RGB格式 */
byte[] current = RemoteUtil.decodeYUV420SP2RGB(data, 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){
System.out.println("---- decode error : "+e);
}
final int spent = (int)(System.currentTimeMillis() - ctime );
try{
Thread.sleep(Math.max(0, timeBetweenFrames - spent));
}catch(InterruptedException e){
System.out.println("sleep error : "+e);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
} @Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera = getCameraInstance();
camera.setPreviewDisplay(surfaceHolder);
camera.setPreviewCallback(this);
Camera.Parameters params = camera.getParameters();
params.setPreviewSize(width, height);
camera.setDisplayOrientation(90);
params.set("orientation", "portrait");
params.set("rotation", 90);
camera.setParameters(params);
} catch (IOException e) {
e.printStackTrace();
camera.release();
camera = null;
}
} // 是否有摄像头
@SuppressLint("NewApi")
public Camera getCameraInstance(){
Camera c = null;
try{
c = Camera.open(0);
}catch(Exception e){
System.out.println("getCamera error"+e.getMessage());
}
return c;
} @Override
public void surfaceDestroyed(SurfaceHolder holder) {
if(camera != null){
camera.release();
camera = null;
}
}
// 得到手机支持的尺寸
private Size getOptimalPreviewSize(List<Size> sizes,int w,int h){
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) return null; Size optimalSize = null;
double minDiff = Double.MAX_VALUE; int targetHeight = h; // Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
} // Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
@Override
public boolean isDestroyed() {
RTMPConnection.connection.close();
RTMPConnection.connection_r.close();
if(aCamera.camera != null){
aCamera.camera.stopPreview();
aCamera.camera.release();
aCamera.camera = null;
}
return true;
}/****************/
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
delayedHide(100);
} View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
}; Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mSystemUiHider.hide();
}
}; private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
}
import java.util.Date;
import java.util.Map;import android.content.Context;
import android.os.Handler;import com.example.android_red5_t2.CameraVideo.AndroidCamera;
import com.example.android_red5_t2.FullscreenActivity;
import com.smaxe.uv.Responder;
import com.smaxe.uv.client.INetConnection;
import com.smaxe.uv.client.INetStream;/**
* RTMP Connection
*
* @author Administrator
*
*/
public class RTMPConnection {
private static final String rtmpURL = "rtmp://192.168.1.125/Red5_MeetingLive/";
public static UltraNetConnection connection;
public static UltraNetStream netStream;
public static String message;
public static Context context;
// ---- add
public static String roomId = "";
public static String whois = "";
public static UltraNetConnection connection_r;
public static String room_info = "";
public static boolean isPW = false;
public static Handler handler;
public static void setHandler(Handler handler){
RTMPConnection.handler = handler;
}
public static void ConnectRed5(Context context,String whois) {
RTMPConnection.whois = whois;
connection = new UltraNetConnection();
connectionUtil(connection);
connection.client(new ClientHandler(context));
connection.addEventListener(new NetConnectionListener());
connection.connect(rtmpURL);
}
public static void goingRoom(String user_name,String pwd,String roomURL,boolean isMaster){
String name_pw = "";
if(isMaster){
name_pw = user_name+"|:"+pwd+"|:Master";
}else{
name_pw = user_name+"|:"+pwd+"|:Client";
}
System.out.println("name_pw:"+name_pw);
connection_r = new UltraNetConnection();
connectionUtil(connection_r);
connection_r.client(new ClientHandler(context));
connection_r.addEventListener(new NetConnectionListener_r());
connection_r.connect(roomURL, name_pw);
}
private static void connectionUtil(UltraNetConnection connection){
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);
}
/**
* 外部调用服务器方法
* @param funName
*/
public static void connect_CreateRoom(String roomId){
connection.call("createRoom", responder, roomId);
room_info = roomId+"|:"+whois+"|:"+new SimpleDateFormat("hh:mm:ss").format(new Date())+"|:"+(isPW?"Y":"N");
}
/**
* 侦听服务器调用的方法
*/
private static Responder responder = new Responder() {
public void onResult(Object value) {
System.out.println("Method createMeeting result: " + value);
if(value.equals("rename")){
handler.sendEmptyMessage(4);
return;
}
String roomURL = rtmpURL+value;
connection.call("putRoomInfo", null, room_info);
goingRoom(whois, "", roomURL, true);
}
public void onStatus(Map<String, Object> arg0) {
// TODO Auto-generated method stub
System.out.println("Method createMeetiong status: " + arg0);
}
};
/**
* 接收服务器回调的函数
* @author WUQING
*
*/
private static class ClientHandler extends Object {
private Context context;
ClientHandler(Context context) {
this.context = context;
};
public void onBWCheck(){}
public void onBWCheck(Object[] paramArrayOfObject){}
public void onBWDone(){}
public void onBWDone(Object[] paramArrayOfObject) {}
// 服务器返回 已有房间信息 显示到主页面
public static void create_newRoom(String str){
roomId = str.split("\\|:")[0];
System.out.println("\n\n\nserverbackof_room --------: ["+str+"]\n\n\n");
}
public static void callJS_back_app(String str){
System.out.println("\n\n\nserverbackof_app --------: ["+str+"]\n\n\n");
}
}
/**
* 大厅状态侦听
* @author WUQING
*
*/
private static class NetConnectionListener extends UltraNetConnection.ListenerAdapter {
public NetConnectionListener() {}
@Override
public void onNetStatus(final INetConnection source, final Map<String, Object> info) {
System.out.println("NetConnection#onNetStatus: " + info);
/*
* 当连接成功的时候 启用 source.call(function,Responder,Object);
* 来调用 服务器中的方法
*/
final Object code = info.get("code");
if (UltraNetConnection.CONNECT_SUCCESS.equals(code)) {
// connection success
handler.sendEmptyMessage(1);
}else{
// connection failed
handler.sendEmptyMessage(3);
}
}
}
/**
* 房间状态侦听
* @author Administrator
*
*/
private static class NetConnectionListener_r extends UltraNetConnection.ListenerAdapter {
public NetConnectionListener_r() {}
@Override
public void onNetStatus(final INetConnection source, final Map<String, Object> info) {
System.out.println("NetConnection#onNetStatus: " + info);
/*
* 当连接成功的时候 启用 source.call(function,Responder,Object);
* 来调用 服务器中的方法
*/
final Object code = info.get("code");
if (UltraNetConnection.CONNECT_SUCCESS.equals(code)) {
System.out.println("--------connection conn success--------");
// 标识自己为master 让别人接收到.
connection_r.call("addClientvideo",null,whois);
startVideo();
}
}
}
/**
* 开始发布
*/
private static void startVideo() { System.out.println("-----------------------connection_r:\t"+connection_r);
netStream = new UltraNetStream(connection_r);
netStream.addEventListener(new UltraNetStream.ListenerAdapter() {
@Override
public void onNetStatus(final INetStream source, final Map<String, Object> info){
System.out.println("Publisher#NetStream#onNetStatus: " + info);
final Object code = info.get("code");
if (UltraNetStream.PUBLISH_START.equals(code)) {
if (FullscreenActivity.aCamera != null) {
netStream.attachCamera(FullscreenActivity.aCamera, -1);
// netStream.attachAudio(netStream.getMicrophone());
FullscreenActivity.aCamera.camera.startPreview();
} else {
}
}
}
});
netStream.publish(whois, UltraNetStream.LIVE);//"mp4:"+User.id + message+".mp4"
}
//invoke server method createMeeting
public static void invokeMethodFormRed5(String toUserId) {
Date nowDate = new Date();
String time = nowDate.getTime() + "" + (int)((Math.random()*100)%100);
message = time;
}
//invoke server method reject
public static void invokeRejectMethod() {
}
private static void callback_createMeeting() {
}
//invoke server method enterMeeting
public static void invokeEnterMeetingMethod() {
//connection.call("enterMeeting", enterResp, message, User.id);
}
private static Responder enterResp = new Responder() {
public void onResult(Object arg0) {
// TODO Auto-generated method stub
System.out.println("Method enterMeeting result: " + arg0);
callback_enterMeeting();
}
public void onStatus(Map<String, Object> arg0) {
// TODO Auto-generated method stub
System.out.println("Method enterMeetiong status: " + arg0);
}
};
private static void callback_enterMeeting() {
}}
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;/**
* RTMP decord
* @author Administrator
*
*/
public class RemoteUtil {
private static Deflater deflater = new Deflater();
public static byte[] decodeYUV420SP2RGB(byte[] yuv420sp, int width, int height) {
final int frameSize = width * height;
byte[] rgbBuf = new byte[frameSize * 3];
// if (rgbBuf == null) throw new NullPointerException("buffer 'rgbBuf' is null");
if (rgbBuf.length < frameSize * 3) throw new IllegalArgumentException("buffer 'rgbBuf' size " + rgbBuf.length + " < minimum " + frameSize * 3);
if (yuv420sp == null) throw new NullPointerException("buffer 'yuv420sp' is null");
if (yuv420sp.length < frameSize * 3 / 2) throw new IllegalArgumentException("buffer 'yuv420sp' size " + yuv420sp.length + " < minimum " + frameSize * 3 / 2);
int i = 0, y = 0;
int uvp = 0, u = 0, v = 0;
int y1192 = 0, r = 0, g = 0, b = 0;
for (int j = 0, yp = 0; j < height; j++) {
uvp = frameSize + (j >> 1) * width;
u = 0;
v = 0;
for (i = 0; i < width; i++, yp++) {
y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0) y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
y1192 = 1192 * y;
r = (y1192 + 1634 * v);
g = (y1192 - 833 * v - 400 * u);
b = (y1192 + 2066 * u);
if (r < 0) r = 0; else if (r > 262143) r = 262143;
if (g < 0) g = 0; else if (g > 262143) g = 262143;
if (b < 0) b = 0; else if (b > 262143) b = 262143;
rgbBuf[yp * 3] = (byte)(r >> 10);
rgbBuf[yp * 3 + 1] = (byte)(g >> 10);
rgbBuf[yp * 3 + 2] = (byte)(b >> 10);
/**
现在的编码出来的颜色是反的 如 蓝色成了橙色,黄的是红的
* 尝试 b g r 顺序
*/
}
}//for
return rgbBuf;
}// decodeYUV420Sp2RGB
public static byte[] decodeYUV420SP2YUV420(byte[]data,int length) {
int width = 176;
int height = 144;
byte[] str = new byte[length];
System.arraycopy(data, 0, str, 0,width*height); int strIndex = width*height; for(int i = width*height+1; i < length ;i+=2) {
str[strIndex++] = data[i];
}
for(int i = width*height;i<length;i+=2) {
str[strIndex++] = data[i];
}
return str;
} //YUV420SP2YUV420
public static byte[] encode(final byte[] current, final byte[] previous, final int blockWidth, final int blockHeight, final int width, final int height) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024); if (previous == null) {
baos.write(getTag(0x01 /* key-frame */, 0x03 /* ScreenVideo codec */));
} else {
baos.write(getTag(0x02 /* inter-frame */, 0x03 /* ScreenVideo codec */));
} // write header
final int wh = width + ((blockWidth / 16 - 1) << 12);
final int hh = height + ((blockHeight / 16 - 1) << 12); writeShort(baos, wh);
writeShort(baos, hh); // write content
int y0 = height;
int x0 = 0;
int bwidth = blockWidth;
int bheight = blockHeight; while (y0 > 0) {
bheight = Math.min(y0, blockHeight);
y0 -= bheight; bwidth = blockWidth;
x0 = 0; while (x0 < width) {
bwidth = (x0 + blockWidth > width) ? width - x0 : blockWidth; final boolean changed = isChanged(current, previous, x0, y0, bwidth, bheight, width, height); if (changed) {
ByteArrayOutputStream blaos = new ByteArrayOutputStream(4 * 1024); DeflaterOutputStream dos = new DeflaterOutputStream(blaos, deflater); for (int y = 0; y < bheight; y++) {
//Log.i("DEBUG", "current的长度:"+current.length+" 起始点:"+3 * ((y0 + bheight - y - 1) * width + x0)+" 终点:"+3 * bwidth);
dos.write(current, 3 * ((y0 + bheight - y - 1) * width + x0), 3 * bwidth);
}
// dos.write(current, 0, current.length);
dos.finish();
deflater.reset(); final byte[] bbuf = blaos.toByteArray();
final int written = bbuf.length; // write DataSize
writeShort(baos, written);
// write Data
baos.write(bbuf, 0, written);
} else {
// write DataSize
writeShort(baos, 0);
}
x0 += bwidth;
}
}
return baos.toByteArray();
}
/**
* Writes short value to the {@link OutputStream <tt>os</tt>}.
*
* @param os
* @param n
* @throws Exception if an exception occurred
*/
private static void writeShort(OutputStream os, final int n) throws Exception {
os.write((n >> 8) & 0xFF);
os.write((n >> 0) & 0xFF);
}
/**
* Checks if image block is changed.
*
* @param current
* @param previous
* @param x0
* @param y0
* @param blockWidth
* @param blockHeight
* @param width
* @param height
* @return <code>true</code> if changed, otherwise <code>false</code>
*/
public static boolean isChanged(final byte[] current, final byte[] previous, final int x0, final int y0, final int blockWidth, final int blockHeight, final int width, final int height) {
if (previous == null)
return true; for (int y = y0, ny = y0 + blockHeight; y < ny; y++) {
final int foff = 3 * (x0 + width * y);
final int poff = 3 * (x0 + width * y); for (int i = 0, ni = 3 * blockWidth; i < ni; i++) {
if (current[foff + i] != previous[poff + i])
return true;
}
}
return false;
}
/**
* @param frame
* @param codec
* @return tag
*/
public static int getTag(final int frame, final int codec) {
return ((frame & 0x0F) << 4) + ((codec & 0x0F) << 0);
}
}
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;public class MyRecorder extends Activity implements OnClickListener {
public static final int STOPPED = 0;
public static final int RECORDING = 1;
PcmRecorder recorderInstance = null; Button startButton = null;
Button stopButton = null;
Button exitButon = null;
TextView textView = null;
int status = STOPPED;
public void onClick(View v) {
try{
if (v == startButton) {
this.setTitle("开始录音了!");
if(recorderInstance == null){
System.out.println("main");
recorderInstance = new PcmRecorder(); // 实例化PcmRecord 编码和获取声音源数据
System.out.println("main 2");
Thread th = new Thread(recorderInstance);
System.out.println("main 3");
th.start();
System.out.println("main 4");
}
recorderInstance.setRecording(true);
}
if (v == stopButton) {
this.setTitle("停止了");
recorderInstance.setRecording(false);
}
if (v == exitButon) {
recorderInstance.setRecording(false);
System.exit(0);
}
}catch(Exception e ){
System.out.println("msg: "+e);
}
} /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
startButton = new Button(this);
stopButton = new Button(this);
exitButon = new Button(this);
textView = new TextView(this);
startButton.setText("Start");
stopButton.setText("Stop");
exitButon.setText("退出");
textView.setText("android 录音机:\n(1)获取PCM数据." +
"\n(2)使用speex编码" +
"\n(3)打包成flv格式" +
"\n(4)发布到流媒体服务器");
startButton.setOnClickListener(this);
stopButton.setOnClickListener(this);
exitButon.setOnClickListener(this);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(textView);
layout.addView(startButton);
layout.addView(stopButton);
layout.addView(exitButon);
this.setContentView(layout);
}
}PcmRecord类: 该类进行编码和声音来源获取package com.ryong21.io;import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;import com.ryong21.encode.Encoder;public class PcmRecorder implements Runnable { private volatile boolean isRecording;
private final Object mutex = new Object();
private static final int frequency = 8000;
private static final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; public PcmRecorder() {
super();
System.out.println("rec super");
} public void run() {
System.out.println("rec run");
// 启动编码线程
Encoder encoder = new Encoder(); // 实例化编码类 该类引用了 speex 外部编码链接
Thread encodeThread = new Thread(encoder);
encoder.setRecording(true);
encodeThread.start();
System.out.println("rec encodestart");
synchronized (mutex) {
System.out.println("rec mutex "+this.isRecording);
while (!this.isRecording) {
try {
mutex.wait();
} catch (InterruptedException e) {
System.out.println("rec Interrupted "+e);
throw new IllegalStateException("Wait() interrupted!", e);
}
}
}
System.out.println("rec mutexed");
android.os.Process
.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); int bufferRead = 0;
int bufferSize = AudioRecord.getMinBufferSize(frequency,
AudioFormat.CHANNEL_IN_MONO, audioEncoding);
System.out.println("rec buffersize");
short[] tempBuffer = new short[bufferSize]; /**
获取声音源
**/
AudioRecord recordInstance = new AudioRecord(
MediaRecorder.AudioSource.MIC, frequency,
AudioFormat.CHANNEL_IN_MONO, audioEncoding, bufferSize);
System.out.println("rec startrec");
recordInstance.startRecording();
System.out.println("while "+isRecording);
while (this.isRecording) { bufferRead = recordInstance.read(tempBuffer, 0, bufferSize);
System.out.println("------------------ buffered -------------\n"+bufferSize+"\n"+bufferRead+"\n-----------------------------------\n");
if (bufferRead == AudioRecord.ERROR_INVALID_OPERATION) {System.out.println("returned AudioRecord.ERROR_INVALID_OPERATION ");
throw new IllegalStateException(
"read() returned AudioRecord.ERROR_INVALID_OPERATION");
} else if (bufferRead == AudioRecord.ERROR_BAD_VALUE) {System.out.println("returned AudioRecord.ERROR_BAD_VALUE");
throw new IllegalStateException(
"read() returned AudioRecord.ERROR_BAD_VALUE");
} else if (bufferRead == AudioRecord.ERROR_INVALID_OPERATION) {System.out.println("returned AudioRecord.ERROR_INVALID_OPERATION");
throw new IllegalStateException(
"read() returned AudioRecord.ERROR_INVALID_OPERATION");
}
System.out.println("encoder isIdle :"+encoder.isIdle());
if (encoder.isIdle()) {
encoder.putData(System.currentTimeMillis(), tempBuffer,
bufferRead);
} else {
// 认为编码处理不过来,直接丢掉这次读到的数据
System.out.println("认为编码处理不过来,直接丢掉这次读到的数据");
} }
System.out.println("rec stop");
recordInstance.stop();
encoder.setRecording(false);
} public void setRecording(boolean isRecording) {
synchronized (mutex) {
this.isRecording = isRecording;
if (this.isRecording) {
mutex.notify();
}
}
} public boolean isRecording() {
synchronized (mutex) {
return isRecording;
}
}
}Encode类: 对音频进行编码类package com.ryong21.encode;import com.ryong21.io.Consumer;
import com.ryong21.io.net.Publisher;public class Encoder implements Runnable { private volatile int leftSize = 0;
private final Object mutex = new Object();
private Speex speex = new Speex();
private int frameSize;
private long ts;
private byte[] processedData = new byte[1024];
private short[] rawdata = new short[1024];
private volatile boolean isRecording; public Encoder() {
super();
System.out.println("encode supered");
speex.init();
System.out.println("encode init");
frameSize = speex.getFrameSize(); //引用 .so 动态链接库
System.out.println("encode framesize: "+frameSize); } public void run() {
System.out.println("encode run");
// 启动publisher线程写发布流媒体。
Consumer consumer = new Publisher(); // 启动publisher 流对音频数据进行上传 引用了 publishClient 类
System.out.println("encode consumer new Publisher");
Thread consumerThread = new Thread((Runnable) consumer);
System.out.println("encode thread consumerThread");
consumer.setRecording(true);
System.out.println("encode consumerThread start");
consumerThread.start();
System.out.println("encode consumer start");
android.os.Process
.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); int getSize = 0;
while (this.isRecording()) {
synchronized (mutex) {
while (isIdle()) {
try {
mutex.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (mutex) {
getSize = speex.encode(rawdata, 0, processedData, leftSize);
System.out.println("encode getSize: "+getSize);
setIdle();
}
// 将编码后的数据传给publisher线程。
if (getSize > 0) {
consumer.putData(ts, processedData, getSize);
}
}
consumer.setRecording(false);
} public void putData(long ts, short[] data, int size) {
synchronized (mutex) {
this.ts = ts;
System.arraycopy(data, 0, rawdata, 0, size);
this.leftSize = size;
mutex.notify();
}
} public boolean isIdle() {
return leftSize == 0 ? true : false;
} public void setIdle() {
leftSize = 0;
} public void setRecording(boolean isRecording) {
synchronized (mutex) {
this.isRecording = isRecording;
if (this.isRecording) {
mutex.notify();
}
}
} public boolean isRecording() {
synchronized (mutex) {
return isRecording;
}
}
}
speex 类
package com.ryong21.encode;
class Speex { /* quality
* 1 : 4kbps (very noticeable artifacts, usually intelligible)
* 2 : 6kbps (very noticeable artifacts, good intelligibility)
* 4 : 8kbps (noticeable artifacts sometimes)
* 6 : 11kpbs (artifacts usually only noticeable with headphones)
* 8 : 15kbps (artifacts not usually noticeable)
*/
private static final int DEFAULT_COMPRESSION = 8; Speex() {
} public void init() {
load();
open(DEFAULT_COMPRESSION);
}
private void load() {
try {
System.loadLibrary("speex");
} catch (Throwable e) {
e.printStackTrace();
} } public native int open(int compression);
public native int getFrameSize();
public native int decode(byte encoded[], short lin[], int size);
public native int encode(short lin[], int offset, byte encoded[], int size);
public native void close();
}
import java.util.LinkedList;
import java.util.List;import org.red5.server.messaging.IMessage;import com.ryong21.io.Consumer;public class Publisher implements Consumer, Runnable{
private final Object mutex = new Object();
private PublishClient client = new PublishClient();
private volatile boolean isRecording;
private processedData pData;
private List<processedData> list;
private String publishName = "test";
private String host = "192.168.1.111";
private int port = 1935;
private String app = "Red5_MeetingLive";
IMessage msg = null;
int timestamp = 0;
int lastTS = 0; public Publisher() {
super();
System.out.println("pubilsher supered");
list = Collections.synchronizedList(new LinkedList<processedData>());
client.setHost(host);
client.setPort(port);
client.setApp(app);
client.setChannle(1);
client.setSampleRate(8000);
System.out.println("publisher client start");
client.start(publishName, "record", null);
System.out.println("publisher client started");
} public void run() {
System.out.println("publisher run :"+isRecording);
while (this.isRecording()) {
if(list.size() > 0){
pData = list.remove(0);
client.writeTag(pData.processed, pData.size, pData.ts);
} else {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
stop();
} public void putData(long ts, byte[] buf, int size) {
System.out.println("publisher putdata :"+size);
processedData data = new processedData();
data.ts = ts;
data.size = size;
System.arraycopy(buf, 0, data.processed, 0, size);
list.add(data);
} public void stop() {
client.stop();
} public void setRecording(boolean isRecording) {
synchronized (mutex) {
this.isRecording = isRecording;
if (this.isRecording) {
mutex.notify();
}
}
} public boolean isRecording() {
synchronized (mutex) {
return isRecording;
}
}
class processedData {
private long ts;
private int size;
private byte[] processed = new byte[256];
}
}
PublishClient 类package com.ryong21.io.net;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.IoConstants;
import org.red5.io.flv.Tag;
import org.red5.io.utils.ObjectMap;
import org.red5.server.messaging.IMessage;
import org.red5.server.net.rtmp.INetStreamEventHandler;
import org.red5.server.net.rtmp.RTMPClient;
import org.red5.server.net.rtmp.event.AudioData;
import org.red5.server.net.rtmp.event.FlexStreamSend;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Invoke;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.event.Unknown;
import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.net.rtmp.message.Constants;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.red5.server.service.IPendingServiceCall;
import org.red5.server.service.IPendingServiceCallback;
import org.red5.server.stream.message.RTMPMessage;/**
* A publish client to publish stream to server.
*/
public class PublishClient implements INetStreamEventHandler,
IPendingServiceCallback {
private List<IMessage> frameBuffer = new ArrayList<IMessage>(); public static final int STOPPED = 0; public static final int CONNECTING = 1; public static final int STREAM_CREATING = 2; public static final int PUBLISHING = 3; public static final int PUBLISHED = 4; private String host; private int port; private String app; private int state; private String publishName; private int streamId; private String publishMode; private RTMPClient rtmpClient; private int prevSize = 0;
private Tag tag;
private int currentTime = 0;
private long timeBase = 0;
private int sampleRate = 0;
private int channle; public int getState() {
return state;
} public void setHost(String host) {
this.host = host;
} public void setPort(int port) {
this.port = port;
} public void setApp(String app) {
this.app = app;
} public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
} public void setChannle(int channle) {
this.channle = channle;
} public PublishClient(){
System.out.println("publishClient");
}
public synchronized void start(String publishName, String publishMode,
Object[] params) {
state = CONNECTING;
this.publishName = publishName;
this.publishMode = publishMode;
rtmpClient = new RTMPClient();
try{
Map<String, Object> defParams = rtmpClient.makeDefaultConnectionParams(
host, port, app);
rtmpClient.connect(host, port, defParams, this, params);
}catch(Exception e){
System.out.println("msgg: "+e);
}
} public synchronized void stop() {
if (state >= STREAM_CREATING) {
rtmpClient.disconnect();
}
state = STOPPED;
} public void writeTag(byte[] buf, int size, long ts) {
if (timeBase == 0) {
timeBase = ts;
}
currentTime = (int) (ts - timeBase);
tag = new Tag(IoConstants.TYPE_AUDIO, currentTime, size + 1, null,
prevSize);
prevSize = size + 1; byte tagType = (byte) ((IoConstants.FLAG_FORMAT_SPEEX << 4))
| (IoConstants.FLAG_SIZE_16_BIT << 1);
switch (sampleRate) {
case 44100:
tagType |= IoConstants.FLAG_RATE_44_KHZ << 2;
break;
case 22050:
tagType |= IoConstants.FLAG_RATE_22_KHZ << 2;
break;
case 11025:
tagType |= IoConstants.FLAG_RATE_11_KHZ << 2;
break;
default:
tagType |= IoConstants.FLAG_RATE_5_5_KHZ << 2;
} tagType |= (channle == 2 ? IoConstants.FLAG_TYPE_STEREO
: IoConstants.FLAG_TYPE_MONO); IoBuffer body = IoBuffer.allocate(tag.getBodySize());
body.setAutoExpand(true);
body.put(tagType);
body.put(buf);
body.flip();
body.limit(tag.getBodySize());
tag.setBody(body); IMessage msg = makeMessageFromTag(tag);
try {
pushMessage(msg);
} catch (IOException e) {
e.printStackTrace();
}
} public IMessage makeMessageFromTag(Tag tag) {
IRTMPEvent msg = null;
switch (tag.getDataType()) {
case Constants.TYPE_AUDIO_DATA:
msg = new AudioData(tag.getBody());
break;
case Constants.TYPE_VIDEO_DATA:
msg = new VideoData(tag.getBody());
break;
case Constants.TYPE_INVOKE:
msg = new Invoke(tag.getBody());
break;
case Constants.TYPE_NOTIFY:
msg = new Notify(tag.getBody());
break;
case Constants.TYPE_FLEX_STREAM_SEND:
msg = new FlexStreamSend(tag.getBody());
break;
default:
msg = new Unknown(tag.getDataType(), tag.getBody());
}
msg.setTimestamp(tag.getTimestamp());
RTMPMessage rtmpMsg = new RTMPMessage();
rtmpMsg.setBody(msg);
rtmpMsg.getBody();
return rtmpMsg;
} synchronized public void pushMessage(IMessage message) throws IOException {
if (state >= PUBLISHED && message instanceof RTMPMessage) {
RTMPMessage rtmpMsg = (RTMPMessage) message;
rtmpClient.publishStreamData(streamId, rtmpMsg);
} else {
frameBuffer.add(message);
}
} public synchronized void onStreamEvent(Notify notify) {
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
if (StatusCodes.NS_PUBLISH_START.equals(code)) {
state = PUBLISHED;
while (frameBuffer.size() > 0) {
rtmpClient.publishStreamData(streamId, frameBuffer.remove(0));
}
}
} public synchronized void resultReceived(IPendingServiceCall call) {
if ("connect".equals(call.getServiceMethodName())) {
state = STREAM_CREATING;
rtmpClient.createStream(this);
} else if ("createStream".equals(call.getServiceMethodName())) {
state = PUBLISHING;
Object result = call.getResult();
if (result instanceof Integer) {
Integer streamIdInt = (Integer) result;
streamId = streamIdInt.intValue();
rtmpClient.publish(streamIdInt.intValue(), publishName,
publishMode, this);
} else {
rtmpClient.disconnect();
state = STOPPED;
}
}
}
}
把publisher.java改成hostpot是127.0.0.1 5080了之后干吗?
连基本连接都没搞懂完全调不下去啊!
from被自己蠢死的green hand