Android + red5 + juv-rtmp-client 实现视频直播问题

时间:2022-02-18 03:58:51
最近做 手机到 red5 的视频直播,从网上找了关于这个的代码看了下, 发现只有实现单视频的直播和录制对声音没有处理,还有一个是只对声音进行了处理没有视频.但这两个例子用到的包又不一样.
经典的 juv-rtmp-client.jar 包功能貌似很强大,用到的例子是 MyRTMPClient
但这个只有画面,而且颜色的编码有问题. 

rtmpClient.jar 用到的是 android-record 例子 该例子 code.google上有.
下载地址是这个.
https://code.google.com/p/android-recorder/downloads/list 
我用的例子是 4
Android + red5 + juv-rtmp-client 实现视频直播问题

源码会一并提供下载
http://pan.baidu.com/s/1dDmVCJn

以下是这两个例子中用到的源码
其中涉及到的问题还希望高手指点一下.

58 个解决方案

#1


MyRTMPClient
首界面
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);
}
}

#2


RTMPConnection 连接类:这里不知道该如何添加声音 声音格式是 IMicphone 类型的.

package com.example.RTMPConnectionUtil;

import java.text.SimpleDateFormat;
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() {
}

}

#3


视频编码类:

package com.example.RTMPDecordUtil;

import java.io.ByteArrayOutputStream;
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);
    }
}

#4


android-record 的代码:

首页: 点击开始到 PcmRecord 类

package com.ryong21;

import com.ryong21.io.PcmRecorder;
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();

}

#5


Publisher 类:

package com.ryong21.io.net;

import java.util.Collections;
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;
}
}
}
}

#6


收藏了,以后可能用得上 Android + red5 + juv-rtmp-client 实现视频直播问题

#7


先顶再看~~~~~~~~~~~~~

#8


先顶,再看 Android + red5 + juv-rtmp-client 实现视频直播问题

#9


先顶再看~~~~~~~~~~~~~ 

#10


楼主、你的那个视频直播弄好了么?有木有源码?发一份给我看看呀,谢谢。邮箱:1710611646@qq.com

#11


最近也在做这个手机视频功能,通过fms将视频与服务器进行p2p的链接,总是不能成功链接到fms服务上。有成功的源码吗,提供一下,谢谢 358116738@qq.com

#12


头痛啊,希望和楼主交流一下red5的问题

#13


楼主你的SystemUiHider类可不可以贴出来。。。。最好把布局文件也贴出来

#14


先顶再看~~~~~~~~~~~~~ 

#15


求源码谢谢759365392@qq.com

#16


楼主 给给一份 代码  893003449@qq.com

#17


楼主求问怎么和red5连接?!!!!!!!!!!!!!!!!!!!!!!!
把publisher.java改成hostpot是127.0.0.1 5080了之后干吗?
连基本连接都没搞懂完全调不下去啊!
from被自己蠢死的green hand

#18


楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:wupingtanlu@163.com

#19


看得头痛也没看出来什么唉,头痛,

#20


楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:673221859@qq.com

#21


求源码啊,楼主 Android + red5 + juv-rtmp-client 实现视频直播问题,能不能把demo发我邮箱1332105188@qq.com

#22


源码怎么下不到了

#23


请问楼主你实现了音视频直播了吗。网上找的都是实现了视频,并没有实现音频跟视频一起啊。大神求指教啊。

#24


楼主给一份代码参考参考谢谢啊 2424066100@qq.com

#25


楼主,你的百度网盘链接失效了,能给我发一份吗? 谢谢 xyqiao@vip.qq.com

#26


楼主,你的网盘失效了,代码能发给我一份么  727839046@qq.com

#27


楼主求实现的源码、跪谢!!!vijozsoft@163.com Android + red5 + juv-rtmp-client 实现视频直播问题

#28


   老大 急用啊 来一份把

#29


   1403738954@qq.com

#30


可以发给我下吗    xiao220900@163.com

#31


    主啊 也给我一份啊!!急哦 2804743667@qq.com

#32


  大神 RTMPConnectionUtil类 onNetStatus方法  中的connection.call 咋用啊?? 传的参数是??? qq2804743667

#33


该回复于2015-07-01 16:50:30被管理员删除

#34


  最主要的是 服务器端咋接收 调用传过来的流媒体

#35


楼主  demo  失效了 麻烦 发给我一份 好吗? 328027284@qq.com

#36


这段时间正在学习相关的,求源码啊,网上资源真的很少啊,拜托啊 576204520@qq.com

#37


正在学习这个,难得找到一个大神,下载还失效了,求源码啊楼主 576204520@qq.com 谢谢

#38


大神,下载还失效了,求源码啊楼主 1355199450@qq.com 谢谢

#39


楼主大神啊,网盘失效了。求源码,邮箱:orphenleoli@163.com, 谢谢

#40


颜色编码的问题只要把r和b互换一下就好了

#41


楼主大神,网盘链接不能用了,求发邮箱,992260173@qq.com

#42


楼主,链接失效了。方便给我发一份吗?  494928699@qq.com

#43


该回复于2016-01-06 09:21:29被管理员删除

#44


大神,求源码!xudong15950500210@163.com

#45


楼主大哥,最近研究的头疼,发一下demo吧,谢了!273089055@qq.com

#46


项目中要用到这个功能求助源码大神   邮箱:christian_zs@163.com  谢谢

#47


楼主Demo给我发一份。拜托了!急需要        138521351@qq.com

#48


项目中要用到这个功能求助源码   邮箱:ll201lw@126.com  谢谢

#49


楼主大神,求源码,,,邮箱:962239291@qq.com  谢谢

#50


我找了好久了,楼主好人!1678026322@qq.com

#1


MyRTMPClient
首界面
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);
}
}

#2


RTMPConnection 连接类:这里不知道该如何添加声音 声音格式是 IMicphone 类型的.

package com.example.RTMPConnectionUtil;

import java.text.SimpleDateFormat;
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() {
}

}

#3


视频编码类:

package com.example.RTMPDecordUtil;

import java.io.ByteArrayOutputStream;
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);
    }
}

#4


android-record 的代码:

首页: 点击开始到 PcmRecord 类

package com.ryong21;

import com.ryong21.io.PcmRecorder;
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();

}

#5


Publisher 类:

package com.ryong21.io.net;

import java.util.Collections;
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;
}
}
}
}

#6


收藏了,以后可能用得上 Android + red5 + juv-rtmp-client 实现视频直播问题

#7


先顶再看~~~~~~~~~~~~~

#8


先顶,再看 Android + red5 + juv-rtmp-client 实现视频直播问题

#9


先顶再看~~~~~~~~~~~~~ 

#10


楼主、你的那个视频直播弄好了么?有木有源码?发一份给我看看呀,谢谢。邮箱:1710611646@qq.com

#11


最近也在做这个手机视频功能,通过fms将视频与服务器进行p2p的链接,总是不能成功链接到fms服务上。有成功的源码吗,提供一下,谢谢 358116738@qq.com

#12


头痛啊,希望和楼主交流一下red5的问题

#13


楼主你的SystemUiHider类可不可以贴出来。。。。最好把布局文件也贴出来

#14


先顶再看~~~~~~~~~~~~~ 

#15


求源码谢谢759365392@qq.com

#16


楼主 给给一份 代码  893003449@qq.com

#17


楼主求问怎么和red5连接?!!!!!!!!!!!!!!!!!!!!!!!
把publisher.java改成hostpot是127.0.0.1 5080了之后干吗?
连基本连接都没搞懂完全调不下去啊!
from被自己蠢死的green hand

#18


楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:wupingtanlu@163.com

#19


看得头痛也没看出来什么唉,头痛,

#20


楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:673221859@qq.com

#21


求源码啊,楼主 Android + red5 + juv-rtmp-client 实现视频直播问题,能不能把demo发我邮箱1332105188@qq.com

#22


源码怎么下不到了

#23


请问楼主你实现了音视频直播了吗。网上找的都是实现了视频,并没有实现音频跟视频一起啊。大神求指教啊。

#24


楼主给一份代码参考参考谢谢啊 2424066100@qq.com

#25


楼主,你的百度网盘链接失效了,能给我发一份吗? 谢谢 xyqiao@vip.qq.com

#26


楼主,你的网盘失效了,代码能发给我一份么  727839046@qq.com

#27


楼主求实现的源码、跪谢!!!vijozsoft@163.com Android + red5 + juv-rtmp-client 实现视频直播问题

#28


   老大 急用啊 来一份把

#29


   1403738954@qq.com

#30


可以发给我下吗    xiao220900@163.com

#31


    主啊 也给我一份啊!!急哦 2804743667@qq.com

#32


  大神 RTMPConnectionUtil类 onNetStatus方法  中的connection.call 咋用啊?? 传的参数是??? qq2804743667

#33


该回复于2015-07-01 16:50:30被管理员删除

#34


  最主要的是 服务器端咋接收 调用传过来的流媒体

#35


楼主  demo  失效了 麻烦 发给我一份 好吗? 328027284@qq.com

#36


这段时间正在学习相关的,求源码啊,网上资源真的很少啊,拜托啊 576204520@qq.com

#37


正在学习这个,难得找到一个大神,下载还失效了,求源码啊楼主 576204520@qq.com 谢谢

#38


大神,下载还失效了,求源码啊楼主 1355199450@qq.com 谢谢

#39


楼主大神啊,网盘失效了。求源码,邮箱:orphenleoli@163.com, 谢谢

#40


颜色编码的问题只要把r和b互换一下就好了

#41


楼主大神,网盘链接不能用了,求发邮箱,992260173@qq.com

#42


楼主,链接失效了。方便给我发一份吗?  494928699@qq.com

#43


该回复于2016-01-06 09:21:29被管理员删除

#44


大神,求源码!xudong15950500210@163.com

#45


楼主大哥,最近研究的头疼,发一下demo吧,谢了!273089055@qq.com

#46


项目中要用到这个功能求助源码大神   邮箱:christian_zs@163.com  谢谢

#47


楼主Demo给我发一份。拜托了!急需要        138521351@qq.com

#48


项目中要用到这个功能求助源码   邮箱:ll201lw@126.com  谢谢

#49


楼主大神,求源码,,,邮箱:962239291@qq.com  谢谢

#50


我找了好久了,楼主好人!1678026322@qq.com