经典的 juv-rtmp-client.jar 包功能貌似很强大,用到的例子是 MyRTMPClient
但这个只有画面,而且颜色的编码有问题.
rtmpClient.jar 用到的是 android-record 例子 该例子 code.google上有.
下载地址是这个.
https://code.google.com/p/android-recorder/downloads/list
我用的例子是 4
源码会一并提供下载
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 类
PcmRecord类: 该类进行编码和声音来源获取
Encode类: 对音频进行编码类
speex 类
首页: 点击开始到 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 类:
PublishClient 类
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
收藏了,以后可能用得上
#7
先顶再看~~~~~~~~~~~~~
#8
先顶,再看
#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
把publisher.java改成hostpot是127.0.0.1 5080了之后干吗?
连基本连接都没搞懂完全调不下去啊!
from被自己蠢死的green hand
#18
楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:wupingtanlu@163.com
#19
看得头痛也没看出来什么唉,头痛,
#20
楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:673221859@qq.com
#21
求源码啊,楼主
,能不能把demo发我邮箱1332105188@qq.com
#22
源码怎么下不到了
#23
请问楼主你实现了音视频直播了吗。网上找的都是实现了视频,并没有实现音频跟视频一起啊。大神求指教啊。
#24
楼主给一份代码参考参考谢谢啊 2424066100@qq.com
#25
楼主,你的百度网盘链接失效了,能给我发一份吗? 谢谢 xyqiao@vip.qq.com
#26
楼主,你的网盘失效了,代码能发给我一份么 727839046@qq.com
#27
楼主求实现的源码、跪谢!!!vijozsoft@163.com
#28
老大 急用啊 来一份把
#29
1403738954@qq.com
#30
可以发给我下吗 xiao220900@163.com
#31
主啊 也给我一份啊!!急哦 2804743667@qq.com
#32
大神 RTMPConnectionUtil类 onNetStatus方法 中的connection.call 咋用啊?? 传的参数是??? qq2804743667
#33
#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
#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 类
PcmRecord类: 该类进行编码和声音来源获取
Encode类: 对音频进行编码类
speex 类
首页: 点击开始到 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 类:
PublishClient 类
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
收藏了,以后可能用得上
#7
先顶再看~~~~~~~~~~~~~
#8
先顶,再看
#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
把publisher.java改成hostpot是127.0.0.1 5080了之后干吗?
连基本连接都没搞懂完全调不下去啊!
from被自己蠢死的green hand
#18
楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:wupingtanlu@163.com
#19
看得头痛也没看出来什么唉,头痛,
#20
楼主、你的那个视频直播弄好了么?有木有源码dome?发一份给我看看呀,谢谢。邮箱:673221859@qq.com
#21
求源码啊,楼主
,能不能把demo发我邮箱1332105188@qq.com
#22
源码怎么下不到了
#23
请问楼主你实现了音视频直播了吗。网上找的都是实现了视频,并没有实现音频跟视频一起啊。大神求指教啊。
#24
楼主给一份代码参考参考谢谢啊 2424066100@qq.com
#25
楼主,你的百度网盘链接失效了,能给我发一份吗? 谢谢 xyqiao@vip.qq.com
#26
楼主,你的网盘失效了,代码能发给我一份么 727839046@qq.com
#27
楼主求实现的源码、跪谢!!!vijozsoft@163.com
#28
老大 急用啊 来一份把
#29
1403738954@qq.com
#30
可以发给我下吗 xiao220900@163.com
#31
主啊 也给我一份啊!!急哦 2804743667@qq.com
#32
大神 RTMPConnectionUtil类 onNetStatus方法 中的connection.call 咋用啊?? 传的参数是??? qq2804743667
#33
#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
#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