解决方案 »

  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.   

    楼主、你的那个视频直播弄好了么?有木有源码?发一份给我看看呀,谢谢。邮箱:[email protected]
      

  7.   

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

  8.   

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

  9.   

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

  10.   

    楼主 给给一份 代码  [email protected]
      

  11.   

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

  12.   

    楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:[email protected]
      

  13.   

    楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:[email protected]
      

  14.   

    求源码啊,楼主,能不能把demo发我邮箱[email protected]