SWCodecCameraStreamingActivity
package com.pili.pldroid.streaming.camera.demo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import com.pili.pldroid.streaming.CameraStreamingManager;
import com.pili.pldroid.streaming.CameraStreamingManager.EncodingType;
import com.pili.pldroid.streaming.WatermarkSetting;
import com.pili.pldroid.streaming.widget.AspectFrameLayout;
/**
* Created by jerikc on 15/10/29.
*/
public class SWCodecCameraStreamingActivity extends StreamingBaseActivity {
private static final String TAG = "SWCodecCameraStreaming";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//用户使用我们的demon apk进入推流摄像界面,屏幕尺寸大小的控制:
AspectFrameLayout afl = (AspectFrameLayout) findViewById(R.id.cameraPreview_afl);
//SHOW_MODE.FULL,可以全屏显示(没有黑边),但是预览的图像和播放的效果有出入,即之前的黑边部分其实没有推流
//SHOW_MODE.REAL,所见即所得
afl.setShowMode(AspectFrameLayout.SHOW_MODE.FULL); // FULL,REAL;
CameraPreviewFrameView cameraPreviewFrameView =
(CameraPreviewFrameView) findViewById(R.id.cameraPreview_surfaceView);
cameraPreviewFrameView.setListener(this);
//------------------------水印参数设置-------------------------------
//第二个参数设置水印图片,可以传入本地drawable中的图片,也可以传入图片的绝对路径作为水印资源
//第三个参数设置水印的位置
//第四个参数设置水印显示的大小
//public WatermarkSetting(Context ctx, String absoluteResPath, WATERMARK_LOCATION location, WATERMARK_SIZE size, int alpha)
WatermarkSetting watermarksetting = new WatermarkSetting(this, R.drawable.qiniu_logo, WatermarkSetting.WATERMARK_LOCATION.NORTH_EAST, WatermarkSetting.WATERMARK_SIZE.MEDIUM, 100);
//构造带有视频 CameraStremaingManager,需要传入AspectFrameLayout,第四个参数设置编码的方式,硬编or软编
mCameraStreamingManager = new CameraStreamingManager(this, afl, cameraPreviewFrameView,
EncodingType.SW_VIDEO_WITH_SW_AUDIO_CODEC); // sw codec
mCameraStreamingManager.prepare(mCameraStreamingSetting, mMicrophoneStreamingSetting, watermarksetting, mProfile);
//-----------注册各种Listener----------------------------------------
//流状态改变回调监听:
//推流状态回调监听,比如流断开,重连回调
mCameraStreamingManager.setStreamingStateListener(this);
//实现StreamStatusCallback 接口的
mCameraStreamingManager.setSurfaceTextureCallback(this);
//可以在 StreamingSessionListener 处理一些重连、音频读取失败、preview size 的自定义操作。
mCameraStreamingManager.setStreamingSessionListener(this);
// mCameraStreamingManager.setNativeLoggingEnabled(false);
mCameraStreamingManager.setStreamStatusCallback(this);
// update the StreamingProfile
// mProfile.setStream(new Stream(mJSONObject1));
// mCameraStreamingManager.setStreamingProfile(mProfile);
setFocusAreaIndicator();
}
}
StreamingBaseActivity
package com.pili.pldroid.streaming.camera.demo;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.pili.pldroid.streaming.CameraStreamingManager;
import com.pili.pldroid.streaming.CameraStreamingSetting;
import com.pili.pldroid.streaming.FrameCapturedCallback;
import com.pili.pldroid.streaming.MicrophoneStreamingSetting;
import com.pili.pldroid.streaming.SharedLibraryNameHelper;
import com.pili.pldroid.streaming.StreamStatusCallback;
import com.pili.pldroid.streaming.StreamingPreviewCallback;
import com.pili.pldroid.streaming.StreamingProfile;
import com.pili.pldroid.streaming.SurfaceTextureCallback;
import com.pili.pldroid.streaming.camera.demo.gles.FBO;
import com.pili.pldroid.streaming.camera.demo.ui.RotateLayout;
import com.qiniu.android.dns.DnsManager;
import com.qiniu.android.dns.IResolver;
import com.qiniu.android.dns.NetworkInfo;
import com.qiniu.android.dns.http.DnspodFree;
import com.qiniu.android.dns.local.AndroidDnsServer;
import com.qiniu.android.dns.local.Resolver;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
/**
* Created by jerikc on 15/7/6.
*/
public class StreamingBaseActivity extends Activity implements
View.OnLayoutChangeListener,
StreamStatusCallback,
StreamingPreviewCallback,
SurfaceTextureCallback,
CameraPreviewFrameView.Listener,
CameraStreamingManager.StreamingSessionListener,
CameraStreamingManager.StreamingStateListener {
private static final String TAG = "StreamingBaseActivity";
private static final int ZOOM_MINIMUM_WAIT_MILLIS = 33; //ms
private Context mContext;
protected Button mShutterButton;
private Button mMuteButton;
private Button mTorchBtn;
private Button mCameraSwitchBtn;
private Button mCaptureFrameBtn;
private Button mEncodingOrientationSwitcherBtn;
private Button mFaceBeautyBtn;
//Filter
private Button mFilterBtn;
private RotateLayout mRotateLayout;
protected TextView mSatusTextView;
private TextView mLogTextView;
private TextView mStreamStatus;
protected boolean mShutterButtonPressed = false;
private boolean mIsTorchOn = false;
private boolean mIsNeedMute = false;
private boolean mIsNeedFB = false;
private boolean mIsNeedFL = false;
private boolean isEncOrientationPort = true;
protected static final int MSG_START_STREAMING = 0;
protected static final int MSG_STOP_STREAMING = 1;
private static final int MSG_SET_ZOOM = 2;
private static final int MSG_MUTE = 3;
private static final int MSG_FB = 4;
private static final int MSG_FL = 5;
protected String mStatusMsgContent;
protected String mLogContent = "\n";
private View mRootView;
protected CameraStreamingManager mCameraStreamingManager;
protected CameraStreamingSetting mCameraStreamingSetting;
protected MicrophoneStreamingSetting mMicrophoneStreamingSetting;
protected StreamingProfile mProfile;
protected JSONObject mJSONObject;
private boolean mIsNeedSpeedCheck = true;
private boolean mOrientationChanged = false;
protected boolean mIsReady = false;
private int mCurrentZoom = 0;
private int mMaxZoom = 0;
private FBO mFBO = new FBO();
private Screenshooter mScreenshooter = new Screenshooter();
private EncodingOrientationSwitcher mEncodingOrientationSwitcher = new EncodingOrientationSwitcher();
protected Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START_STREAMING:
new Thread(new Runnable() {
@Override
public void run() {
// disable the shutter button before startStreaming
setShutterButtonEnabled(false);
boolean res = mCameraStreamingManager.startStreaming();
mShutterButtonPressed = true;
Log.i(TAG, "res:" + res);
if (!res) {
mShutterButtonPressed = false;
setShutterButtonEnabled(true);
}
setShutterButtonPressed(mShutterButtonPressed);
}
}).start();
break;
case MSG_STOP_STREAMING:
// disable the shutter button before stopStreaming
setShutterButtonEnabled(false);
boolean res = mCameraStreamingManager.stopStreaming();
if (!res) {
mShutterButtonPressed = true;
setShutterButtonEnabled(true);
}
setShutterButtonPressed(mShutterButtonPressed);
break;
case MSG_SET_ZOOM:
mCameraStreamingManager.setZoomValue(mCurrentZoom);
break;
case MSG_MUTE:
mIsNeedMute = !mIsNeedMute;
//-----------------禁音推流--------------------------------------
//设置为true为禁止声音推流,默认为falase
mCameraStreamingManager.mute(mIsNeedMute);
updateMuteButtonText();
break;
case MSG_FB:
mIsNeedFB = !mIsNeedFB;
mCameraStreamingManager.setVideoFilterType(mIsNeedFB ?
CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY
: CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_NONE);
updateFBButtonText();
break;
case MSG_FL:
mIsNeedFL = !mIsNeedFL;
onSurfaceCreated();
/*mCameraStreamingManager.setVideoFilterType(mIsNeedFL ?
CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY
: CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_NONE);*/
updateFLButtonText();
break;
default:
Log.e(TAG, "Invalid message");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
} else {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (Config.SCREEN_ORIENTATION == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
isEncOrientationPort = true;
} else if (Config.SCREEN_ORIENTATION == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
isEncOrientationPort = false;
}
setRequestedOrientation(Config.SCREEN_ORIENTATION);
setContentView(R.layout.activity_camera_streaming);
//从MainActivity获取传入的推流的JSON数据
String streamJsonStrFromServer = getIntent().getStringExtra(Config.EXTRA_KEY_STREAM_JSON);
Log.i(TAG, "streamJsonStrFromServer:" + streamJsonStrFromServer);
try {
mJSONObject = new JSONObject(streamJsonStrFromServer);
} catch (Exception e) {
e.printStackTrace();
}
mContext = this;
StreamingProfile.AudioProfile aProfile = new StreamingProfile.AudioProfile(44100, 96 * 1024);
StreamingProfile.VideoProfile vProfile = new StreamingProfile.VideoProfile(30, 1000 * 1024, 48);
StreamingProfile.AVProfile avProfile = new StreamingProfile.AVProfile(vProfile, aProfile);
//------------------------推流参数设置-------------------------------
mProfile = new StreamingProfile();
StreamingProfile.Stream stream = new StreamingProfile.Stream(mJSONObject);
//------------------通过setStream设置推流地址----------------
mProfile.setStream(stream);
//-----------------通过setVideoQuality设置推流视频部分质量参数------------
/*
Level Fps Video Bitrate(Kbps)
VIDEO_QUALITY_LOW1 12 150
VIDEO_QUALITY_LOW2 15 264
VIDEO_QUALITY_LOW3 15 350
VIDEO_QUALITY_MEDIUM1 30 512
VIDEO_QUALITY_MEDIUM2 30 800
VIDEO_QUALITY_MEDIUM3 30 1000
VIDEO_QUALITY_HIGH1 30 1200
VIDEO_QUALITY_HIGH2 30 1500
VIDEO_QUALITY_HIGH3 30 2000
*/
mProfile.setVideoQuality(StreamingProfile.VIDEO_QUALITY_HIGH3)
//-----------------通过setAudioQuality设置推流音频部分质量参数------------
/*
Level Audio Bitrate(Kbps) Audio Sample Rate(Hz)
AUDIO_QUALITY_LOW1 18 44100
AUDIO_QUALITY_LOW2 24 44100
AUDIO_QUALITY_MEDIUM1 32 44100
AUDIO_QUALITY_MEDIUM2 48 44100
AUDIO_QUALITY_HIGH1 96 44100
AUDIO_QUALITY_HIGH2 128 44100
*/
.setAudioQuality(StreamingProfile.AUDIO_QUALITY_MEDIUM2)
//setPreferredVideoEncodingSize 自定义设置推流分辨率,此优先级高于setEncodingSizeLevel
// .setPreferredVideoEncodingSize(960, 544)
.setEncodingSizeLevel(Config.ENCODING_LEVEL)
//--------------通过setEncoderRCMode设置质量优先还是码率优先----------------
//EncoderRCModes.QUALITY_PRIORITY: 质量优先,实际的码率可能高于设置的码率
//EncoderRCModes.BITRATE_PRIORITY: 码率优先,更精确地码率控制
//默认值为 EncoderRCModes.QUALITY_PRIORITY
.setEncoderRCMode(StreamingProfile.EncoderRCModes.QUALITY_PRIORITY)
//--------------通过AVProfile参数自定义推流音视频码率帧率--------------------
// 自定义设置音频采样率为44100Hz, 码率为96 * 1024 bps
// 自定义设置视频帧率为30, 码率为1000 * 1024 bps,最大帧率为48
//setAVProfile 的优先级高于 Quality,也就是说,当同时调用了 Quality 和 AVProfile 的设置,AVProfile 会覆盖 Quality 的设置值
.setAVProfile(avProfile)
.setDnsManager(getMyDnsManager())
.setStreamStatusConfig(new StreamingProfile.StreamStatusConfig(3))
// .setEncodingOrientation(StreamingProfile.ENCODING_ORIENTATION.PORT)
// (lowThreshold,highThreshold,durationLimit,timeout)
.setSendingBufferProfile(new StreamingProfile.SendingBufferProfile(0.2f, 0.8f, 3.0f, 20 * 1000));
//----------------------------摄像头设置----------------------------
mCameraStreamingSetting = new CameraStreamingSetting();
//------------前后置镜头相关--------
//通过setCameraId可以指定使用前置摄像头或者是后置摄像头, CAMERA_FACING_FONT为前置, CAMERA_FACING_BACK为后置
mCameraStreamingSetting.setCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT)
//-------------对焦相关----------
//通过setContinuousFocusModeEnabled设置自动对焦
.setContinuousFocusModeEnabled(true)
//---------------RecordingHint提升数据源的帧率--------
//在部分机型开启 Recording Hint 之后,会出现画面卡帧等风险,所以请慎用该API。如果需要实现高 fps 推流,可以考虑开启并加入白名单机制
.setRecordingHint(false)
.setBuiltInFaceBeautyEnabled(true)
.setResetTouchFocusDelayInMs(3000)
//通过setFocusMode设置对焦模式
/* FOCUS_MODE_CONTINUOUS_PICTURE 自动对焦(Picture);
FOCUS_MODE_CONTINUOUS_VIDEO 自动对焦(Video);
FOCUS_MODE_AUTO 手动对焦*/
// .setFocusMode(CameraStreamingSetting.FOCUS_MODE_CONTINUOUS_PICTURE)
//--------------摄像头预览相关---------
// 使用 PREVIEW_SIZE_LEVEL 和 PREVIEW_SIZE_RATIO 共同确定一个预览Size
//PREVIEW_SIZE_LEVEL和相机预览的清晰度有关系, 设置为SMALL预览的画面会很不清晰
//PREVIEW_SIZE_RATIO 我们的SDK是提供的两种规格16_9 和 4_3
.setCameraPrvSizeLevel(CameraStreamingSetting.PREVIEW_SIZE_LEVEL.MEDIUM)
.setCameraPrvSizeRatio(CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_16_9)
//--------------美颜相关设置---------
//参数说明:1,beautyLevel;2,whiten ;3,redden.
.setFaceBeautySetting(new CameraStreamingSetting.FaceBeautySetting(1.0f, 1.0f, 0.8f))
.setVideoFilter(CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY);
mIsNeedFB = true;
mIsNeedFL = true;
//Filter
//------------------------麦克风参数配置-----------------------------
mMicrophoneStreamingSetting = new MicrophoneStreamingSetting();
//希望增加蓝牙麦克风的支持,可以设置:true
mMicrophoneStreamingSetting.setBluetoothSCOEnabled(false);
initUIs();
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "tid:" + Thread.currentThread().getId());
try {
//CameraStreamingManager#resume 会进行 Camera 的打开操作,当成功打开后,
//会返回 STATE.READY 消息,用户可以在接受到 STATE.READY 之后,安全地进行推流操作。
mCameraStreamingManager.resume();
} catch (Exception e) {
Toast.makeText(StreamingBaseActivity.this, "Device open error!", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onPause() {
super.onPause();
mIsReady = false;
mShutterButtonPressed = false;
//退出 CameraStreamingManager,该操作会主动断开当前的流链接,并关闭 Camera 和释放相应的资源。
mCameraStreamingManager.pause();
mHandler.removeCallbacksAndMessages(null);
}
@Override
protected void onDestroy() {
super.onDestroy();
mCameraStreamingManager.destroy();
}
protected void setShutterButtonPressed(final boolean pressed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mShutterButtonPressed = pressed;
mShutterButton.setPressed(pressed);
}
});
}
protected void setShutterButtonEnabled(final boolean enable) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mShutterButton.setFocusable(enable);
mShutterButton.setClickable(enable);
mShutterButton.setEnabled(enable);
}
});
}
protected void startStreaming() {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_STREAMING), 50);
}
protected void stopStreaming() {
mHandler.removeCallbacksAndMessages(null);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_STOP_STREAMING), 50);
}
@Override
public boolean onRecordAudioFailedHandled(int err) {
mCameraStreamingManager.updateEncodingType(CameraStreamingManager.EncodingType.SW_VIDEO_CODEC);
mCameraStreamingManager.startStreaming();
return true;
}
@Override
public boolean onRestartStreamingHandled(int err) {
Log.i(TAG, "onRestartStreamingHandled");
return mCameraStreamingManager.startStreaming();
}
@Override
public Camera.Size onPreviewSizeSelected(List<Camera.Size> list) {
Camera.Size size = null;
// if (list != null) {
// for (Camera.Size s : list) {
// size = s;
// Log.i(TAG, "w:" + s.width + ", h:" + s.height);
// break;
// if (s.height < 480) {
// continue;
// } else {
// size = s;
// break;
// }
// }
// }
// Log.e(TAG, "selected size :" + size.width + "x" + size.height);
return size;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.i(TAG, "onSingleTapUp X:" + e.getX() + ",Y:" + e.getY());
if (mIsReady) {
setFocusAreaIndicator();
mCameraStreamingManager.doSingleTapUp((int) e.getX(), (int) e.getY());
return true;
}
return false;
}
@Override
public boolean onZoomValueChanged(float factor) {
if (mIsReady && mCameraStreamingManager.isZoomSupported()) {
mCurrentZoom = (int) (mMaxZoom * factor);
mCurrentZoom = Math.min(mCurrentZoom, mMaxZoom);
mCurrentZoom = Math.max(0, mCurrentZoom);
Log.d(TAG, "zoom ongoing, scale: " + mCurrentZoom + ",factor:" + factor + ",maxZoom:" + mMaxZoom);
if (!mHandler.hasMessages(MSG_SET_ZOOM)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ZOOM), ZOOM_MINIMUM_WAIT_MILLIS);
return true;
}
}
return false;
}
private Switcher mSwitcher = new Switcher();
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Log.i(TAG, "view!!!!:" + v);
}
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
}
@Override
public boolean onPreviewFrame(byte[] bytes, int width, int height) {
// deal with the yuv data.
// long start = System.currentTimeMillis();
// for (int i = 0; i < bytes.length; i++) {
// bytes[i] = 0x00;
// }
// Log.i(TAG, "old onPreviewFrame cost :" + (System.currentTimeMillis() - start));
return true;
}
@Override
public void onSurfaceCreated() {
Log.i(TAG, "onSurfaceCreated");
mFBO.initialize(this);
}
@Override
public void onSurfaceChanged(int width, int height) {
Log.i(TAG, "onSurfaceChanged width:" + width + ",height:" + height);
mFBO.updateSurfaceSize(width, height);
}
@Override
public void onSurfaceDestroyed() {
Log.i(TAG, "onSurfaceDestroyed");
mFBO.release();
}
@Override
public int onDrawFrame(int texId, int texWidth, int texHeight, float[] transformMatrix) {
// newTexId should not equal with texId. texId is from the SurfaceTexture.
// Otherwise, there is no filter effect.
int newTexId = mFBO.drawFrame(texId, texWidth, texHeight);
// Log.i(TAG, "onDrawFrame texId:" + texId + ",newTexId:" + newTexId + ",texWidth:" + texWidth + ",texHeight:" + texHeight);
return newTexId;
}
//推流状态信息
@Override
public void notifyStreamStatusChanged(final StreamingProfile.StreamStatus streamStatus) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mStreamStatus.setText("bitrate:" + streamStatus.totalAVBitrate / 1024 + " kbps"
+ "\naudio:" + streamStatus.audioFps + " fps"
+ "\nvideo:" + streamStatus.videoFps + " fps");
}
});
}
private class Switcher implements Runnable {
@Override
public void run() {
mCameraStreamingManager.switchCamera();
}
}
private class EncodingOrientationSwitcher implements Runnable {
@Override
public void run() {
Log.i(TAG, "isEncOrientationPort:" + isEncOrientationPort);
stopStreaming();
mOrientationChanged = !mOrientationChanged;
isEncOrientationPort = !isEncOrientationPort;
//-------------------设置推流播放端方向-------------------------------
//设置ENCODING_ORIENTATION.LAND推流播放端会横屏播放
//设置ENCODING_ORIENTATION.PORT推流播放端会竖屏播放
mProfile.setEncodingOrientation(isEncOrientationPort ? StreamingProfile.ENCODING_ORIENTATION.PORT : StreamingProfile.ENCODING_ORIENTATION.LAND);
mCameraStreamingManager.setStreamingProfile(mProfile);
setRequestedOrientation(isEncOrientationPort ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mCameraStreamingManager.notifyActivityOrientationChanged();
updateOrientationBtnText();
Toast.makeText(StreamingBaseActivity.this, Config.HINT_ENCODING_ORIENTATION_CHANGED,
Toast.LENGTH_SHORT).show();
Log.i(TAG, "EncodingOrientationSwitcher -");
}
}
private class Screenshooter implements Runnable {
@Override
public void run() {
final String fileName = "PLStreaming_" + System.currentTimeMillis() + ".jpg";
//SDK 完成截帧之后,会回调 onFrameCaptured,并将结果以参数的形式返回给调用者。
mCameraStreamingManager.captureFrame(100, 100, new FrameCapturedCallback() {
private Bitmap bitmap;
@Override
public void onFrameCaptured(Bitmap bmp) {
if (bmp == null) {
return;
}
bitmap = bmp;
new Thread(new Runnable() {
@Override
public void run() {
try {
saveToSDCard(fileName, bitmap);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
}
}).start();
}
});
}
}
private void setTorchEnabled(final boolean enabled) {
runOnUiThread(new Runnable() {
@Override
public void run() {
String flashlight = enabled ? getString(R.string.flash_light_off) : getString(R.string.flash_light_on);
mTorchBtn.setText(flashlight);
}
});
}
//推流过程中流状态信息的一些回调
@Override
public void onStateChanged(final int state, Object extra) {
switch (state) {
case CameraStreamingManager.STATE.PREPARING:
mStatusMsgContent = getString(R.string.string_state_preparing);
break;
case CameraStreamingManager.STATE.READY:
// 当流的状态是READY的时候开始推流
mIsReady = true;
mMaxZoom = mCameraStreamingManager.getMaxZoom();
mStatusMsgContent = getString(R.string.string_state_ready);
// start streaming when READY
startStreaming();
break;
case CameraStreamingManager.STATE.CONNECTING:
mStatusMsgContent = getString(R.string.string_state_connecting);
break;
case CameraStreamingManager.STATE.STREAMING:
mStatusMsgContent = getString(R.string.string_state_streaming);
setShutterButtonEnabled(true);
setShutterButtonPressed(true);
break;
case CameraStreamingManager.STATE.SHUTDOWN:
mStatusMsgContent = getString(R.string.string_state_ready);
setShutterButtonEnabled(true);
setShutterButtonPressed(false);
if (mOrientationChanged) {
mOrientationChanged = false;
startStreaming();
}
break;
//若网络不可达(网络断开等情况),尝试重连之后,会返回 STATE.IOERROR,可以不用担心循环重连的问题。
//在网络层会有三次重连的机会,即没有必要在收到 STATE.IOERROR 之后继续进行重连,一般接收到 STATE.IOERROR 代表无法通过网络和服务端建立链接。
case CameraStreamingManager.STATE.IOERROR:
mLogContent += "IOERROR\n";
mStatusMsgContent = getString(R.string.string_state_ready);
setShutterButtonEnabled(true);
break;
case CameraStreamingManager.STATE.UNKNOWN:
mStatusMsgContent = getString(R.string.string_state_ready);
break;
case CameraStreamingManager.STATE.SENDING_BUFFER_EMPTY:
break;
case CameraStreamingManager.STATE.SENDING_BUFFER_FULL:
break;
case CameraStreamingManager.STATE.AUDIO_RECORDING_FAIL:
break;
case CameraStreamingManager.STATE.OPEN_CAMERA_FAIL:
Log.e(TAG, "Open Camera Fail. id:" + extra);
break;
//在网络断开、sendTimeOut 后没有发出数据、网络链接被服务端断开等网络异常后,STATE.DISCONNECTED 消息会被回调
case CameraStreamingManager.STATE.DISCONNECTED:
mLogContent += "DISCONNECTED\n";
break;
case CameraStreamingManager.STATE.INVALID_STREAMING_URL:
Log.e(TAG, "Invalid streaming url:" + extra);
break;
case CameraStreamingManager.STATE.CAMERA_SWITCHED:
// mShutterButtonPressed = false;
if (extra != null) {
Log.i(TAG, "current camera id:" + (Integer)extra);
}
Log.i(TAG, "camera switched");
final int currentCamId = (Integer)extra;
this.runOnUiThread(new Runnable() {
@Override
public void run() {
updateCameraSwitcherButtonText(currentCamId);
}
});
break;
case CameraStreamingManager.STATE.TORCH_INFO:
if (extra != null) {
final boolean isSupportedTorch = (Boolean) extra;
Log.i(TAG, "isSupportedTorch=" + isSupportedTorch);
this.runOnUiThread(new Runnable() {
@Override
public void run() {
if (isSupportedTorch) {
mTorchBtn.setVisibility(View.VISIBLE);
} else {
mTorchBtn.setVisibility(View.GONE);
}
}
});
}
break;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mLogTextView != null) {
mLogTextView.setText(mLogContent);
}
mSatusTextView.setText(mStatusMsgContent);
}
});
}
@Override
public boolean onStateHandled(final int state, Object extra) {
switch (state) {
case CameraStreamingManager.STATE.SENDING_BUFFER_HAS_FEW_ITEMS:
return false;
case CameraStreamingManager.STATE.SENDING_BUFFER_HAS_MANY_ITEMS:
return false;
}
return false;
}
private void initUIs() {
mRootView = findViewById(R.id.content);
mRootView.addOnLayoutChangeListener(this);
mMuteButton = (Button) findViewById(R.id.mute_btn);
mShutterButton = (Button) findViewById(R.id.toggleRecording_button);
mTorchBtn = (Button) findViewById(R.id.torch_btn);
mCameraSwitchBtn = (Button) findViewById(R.id.camera_switch_btn);
mCaptureFrameBtn = (Button) findViewById(R.id.capture_btn);
mFaceBeautyBtn = (Button) findViewById(R.id.fb_btn);
//Filter
mFilterBtn = (Button) findViewById(R.id.fl_btn);
mSatusTextView = (TextView) findViewById(R.id.streamingStatus);
mLogTextView = (TextView) findViewById(R.id.log_info);
//推流状态信息回调mStreamStatus初始化
mStreamStatus = (TextView) findViewById(R.id.stream_status);
mFaceBeautyBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mHandler.hasMessages(MSG_FB)) {
mHandler.sendEmptyMessage(MSG_FB);
}
}
});
//Filter
mFilterBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(StreamingBaseActivity.this, "Sorry,this fuction will up by the next version", Toast.LENGTH_SHORT).show();
/*if (!mHandler.hasMessages(MSG_FL)) {
mHandler.sendEmptyMessage(MSG_FL);
}*/
}
});
mMuteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mHandler.hasMessages(MSG_MUTE)) {
mHandler.sendEmptyMessage(MSG_MUTE);
}
/*客户的操作,在按钮中添加跳转activity的操作,出现黑屏现象???*/
/*Intent intent = new Intent(StreamingBaseActivity.this,MainActivity.class);
startActivity(intent);*/
}
});
mShutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mShutterButtonPressed) {
stopStreaming();
} else {
startStreaming();
}
}
});
mTorchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
if (!mIsTorchOn) {
mIsTorchOn = true;
mCameraStreamingManager.turnLightOn();
} else {
mIsTorchOn = false;
mCameraStreamingManager.turnLightOff();
}
setTorchEnabled(mIsTorchOn);
}
}).start();
}
});
//postDelayed的作用是延迟多少毫秒后开始运行
//removeCallbacks方法是删除指定的Runnable对象,使线程对象停止运行。
mCameraSwitchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mHandler.removeCallbacks(mSwitcher);
mHandler.postDelayed(mSwitcher, 100);
}
});
mCaptureFrameBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mHandler.removeCallbacks(mScreenshooter);
mHandler.postDelayed(mScreenshooter, 100);
}
});
mEncodingOrientationSwitcherBtn = (Button) findViewById(R.id.orientation_btn);
mEncodingOrientationSwitcherBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.removeCallbacks(mEncodingOrientationSwitcher);
mHandler.post(mEncodingOrientationSwitcher);
}
});
//进度条-调节美颜效果程度
SeekBar seekBarBeauty = (SeekBar) findViewById(R.id.beautyLevel_seekBar);
seekBarBeauty.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
CameraStreamingSetting.FaceBeautySetting fbSetting = mCameraStreamingSetting.getFaceBeautySetting();
fbSetting.beautyLevel = progress / 100.0f;
fbSetting.whiten = progress / 100.0f;
fbSetting.redden = progress / 100.0f;
mCameraStreamingManager.updateFaceBeautySetting(fbSetting);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
initButtonText();
}
private void initButtonText() {
updateFLButtonText(); //
updateCameraSwitcherButtonText(mCameraStreamingSetting.getReqCameraId());
mCaptureFrameBtn.setText("截帧"); //截屏 Capture
updateFBButtonText();
updateMuteButtonText();
updateOrientationBtnText();
}
private void updateOrientationBtnText() { //横屏和竖屏
if (isEncOrientationPort) {
mEncodingOrientationSwitcherBtn.setText("切横屏");//横屏 Land
} else {
mEncodingOrientationSwitcherBtn.setText("切竖屏");//竖屏 Port
}
}
protected void setFocusAreaIndicator() {
if (mRotateLayout == null) {
mRotateLayout = (RotateLayout)findViewById(R.id.focus_indicator_rotate_layout);
mCameraStreamingManager.setFocusAreaIndicator(mRotateLayout,
mRotateLayout.findViewById(R.id.focus_indicator));
}
}
private void updateFBButtonText() {
if (mFaceBeautyBtn != null) {
mFaceBeautyBtn.setText(mIsNeedFB ? "关闭美颜" : "开启美颜"); //美颜开关 FB Off FB On
}
}
//Filter
private void updateFLButtonText() {
if (mFilterBtn != null) {
mFilterBtn.setText(mIsNeedFL ? "关闭滤镜" : "开启滤镜"); //滤镜开关 FB Off FB On
}
}
private void updateMuteButtonText() {
if (mMuteButton != null) {
mMuteButton.setText(mIsNeedMute ? "恢复声音" : "静音"); //有声 Unmute or 无声 Mute
}
}
private void updateCameraSwitcherButtonText(int camId) {
if (mCameraSwitchBtn == null) {
return;
}
if (camId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
mCameraSwitchBtn.setText("后摄"); //后摄 Back
} else {
mCameraSwitchBtn.setText("前摄"); //前摄 Front
}
}
private void saveToSDCard(String filename, Bitmap bmp) throws IOException {
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file = new File(Environment.getExternalStorageDirectory(), filename);
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(file));
bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
bmp.recycle();
bmp = null;
} finally {
if (bos != null) bos.close();
}
final String info = "Save frame to:" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + filename;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, info, Toast.LENGTH_LONG).show();
}
});
}
}
private static DnsManager getMyDnsManager() {
IResolver r0 = new DnspodFree();
IResolver r1 = AndroidDnsServer.defaultResolver();
IResolver r2 = null;
try {
r2 = new Resolver(InetAddress.getByName("119.29.29.29"));
} catch (IOException ex) {
ex.printStackTrace();
}
return new DnsManager(NetworkInfo.normal, new IResolver[]{r0, r1, r2});
}
}