package com.qiniu.pili.droid.streaming.demo.activity;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
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.Toast;
import com.qiniu.pili.droid.streaming.CameraStreamingSetting;
import com.qiniu.pili.droid.streaming.FrameCapturedCallback;
import com.qiniu.pili.droid.streaming.MediaStreamingManager;
import com.qiniu.pili.droid.streaming.MicrophoneStreamingSetting;
import com.qiniu.pili.droid.streaming.StreamingPreviewCallback;
import com.qiniu.pili.droid.streaming.StreamingProfile;
import com.qiniu.pili.droid.streaming.StreamingState;
import com.qiniu.pili.droid.streaming.SurfaceTextureCallback;
import com.qiniu.pili.droid.streaming.WatermarkSetting;
import com.qiniu.pili.droid.streaming.av.common.PLFourCC;
import com.qiniu.pili.droid.streaming.demo.R;
import com.qiniu.pili.droid.streaming.demo.gles.FBO;
import com.qiniu.pili.droid.streaming.demo.plain.CameraConfig;
import com.qiniu.pili.droid.streaming.demo.ui.CameraPreviewFrameView;
import com.qiniu.pili.droid.streaming.demo.ui.RotateLayout;
import com.qiniu.pili.droid.streaming.demo.utils.Config;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class AVStreamingActivity extends StreamingBaseActivity implements
StreamingPreviewCallback,
CameraPreviewFrameView.Listener,
SurfaceTextureCallback {
private static final String TAG = "AVStreamingActivity";
private CameraStreamingSetting mCameraStreamingSetting;
private CameraConfig mCameraConfig;
private Button mMuteButton;
private Button mTorchBtn;
private Button mCameraSwitchBtn;
private Button mCaptureFrameBtn;
private Button mEncodingOrientationSwitcherBtn;
private Button mFaceBeautyBtn;
private RotateLayout mRotateLayout;
private boolean mIsTorchOn = false;
private boolean mIsNeedMute = false;
private boolean mIsNeedFB = false;
private boolean mIsEncOrientationPort = true;
private boolean mIsPreviewMirror = false;
private boolean mIsEncodingMirror = false;
private int mCurrentZoom = 0;
private int mMaxZoom = 0;
private boolean mOrientationChanged = false;
private int mCurrentCamFacingIndex;
private FBO mFBO = new FBO();
private ScreenShooter mScreenShooter = new ScreenShooter();
private Switcher mSwitcher = new Switcher();
private EncodingOrientationSwitcher mEncodingOrientationSwitcher = new EncodingOrientationSwitcher();
private MediaStreamingManager mMediaStreamingManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (mEncodingConfig.mPictureStreamingFilePath != null && mIsReady) {
mMediaStreamingManager.togglePictureStreaming();
} else {
mMediaStreamingManager.resume();
}
}
@Override
protected void onPause() {
super.onPause();
if (mEncodingConfig.mPictureStreamingFilePath != null) {
mMediaStreamingManager.togglePictureStreaming();
} else {
normalPause();
}
}
private void normalPause() {
mIsReady = false;
mShutterButtonPressed = false;
mMediaStreamingManager.pause();
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (mEncodingConfig.mPictureStreamingFilePath != null) {
normalPause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mMediaStreamingManager.destroy();
}
@Override
protected void initStreamingManager() {
CameraPreviewFrameView cameraPreviewFrameView = (CameraPreviewFrameView) findViewById(R.id.cameraPreview_surfaceView);
mMediaStreamingManager = new MediaStreamingManager(this, cameraPreviewFrameView, mEncodingConfig.mCodecType);
mProfile.setPictureStreamingFilePath(mEncodingConfig.mPictureStreamingFilePath);
MicrophoneStreamingSetting microphoneStreamingSetting = null;
if (mAudioStereoEnable) {
/**
* Notice !!! {@link AudioFormat#CHANNEL_IN_STEREO} is NOT guaranteed to work on all devices.
*/
microphoneStreamingSetting = new MicrophoneStreamingSetting();
microphoneStreamingSetting.setChannelConfig(AudioFormat.CHANNEL_IN_STEREO);
}
mMediaStreamingManager.prepare(mCameraStreamingSetting, microphoneStreamingSetting, buildWatermarkSetting(), mProfile);
if (mCameraConfig.mIsCustomFaceBeauty) {
cameraPreviewFrameView.setListener(this);
mMediaStreamingManager.setSurfaceTextureCallback(this);
}
mMediaStreamingManager.setStreamingSessionListener(this);
mMediaStreamingManager.setStreamStatusCallback(this);
mMediaStreamingManager.setAudioSourceCallback(this);
mMediaStreamingManager.setStreamingStateListener(this);
}
@Override
protected boolean startStreaming() {
return mMediaStreamingManager.startStreaming();
}
@Override
protected boolean stopStreaming() {
return mMediaStreamingManager.stopStreaming();
}
private class EncodingOrientationSwitcher implements Runnable {
@Override
public void run() {
Log.i(TAG, "mIsEncOrientationPort:" + mIsEncOrientationPort);
mOrientationChanged = true;
mIsEncOrientationPort = !mIsEncOrientationPort;
mProfile.setEncodingOrientation(mIsEncOrientationPort ? StreamingProfile.ENCODING_ORIENTATION.PORT : StreamingProfile.ENCODING_ORIENTATION.LAND);
mMediaStreamingManager.setStreamingProfile(mProfile);
stopStreamingInternal();
setRequestedOrientation(mIsEncOrientationPort ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mMediaStreamingManager.notifyActivityOrientationChanged();
updateOrientationBtnText();
Toast.makeText(AVStreamingActivity.this, Config.HINT_ENCODING_ORIENTATION_CHANGED,
Toast.LENGTH_SHORT).show();
Log.i(TAG, "EncodingOrientationSwitcher -");
}
}
private class Switcher implements Runnable {
@Override
public void run() {
mCurrentCamFacingIndex = (mCurrentCamFacingIndex + 1) % CameraStreamingSetting.getNumberOfCameras();
CameraStreamingSetting.CAMERA_FACING_ID facingId;
if (mCurrentCamFacingIndex == CameraStreamingSetting.CAMERA_FACING_ID.CAMERA_FACING_BACK.ordinal()) {
facingId = CameraStreamingSetting.CAMERA_FACING_ID.CAMERA_FACING_BACK;
} else if (mCurrentCamFacingIndex == CameraStreamingSetting.CAMERA_FACING_ID.CAMERA_FACING_FRONT.ordinal()) {
facingId = CameraStreamingSetting.CAMERA_FACING_ID.CAMERA_FACING_FRONT;
} else {
facingId = CameraStreamingSetting.CAMERA_FACING_ID.CAMERA_FACING_3RD;
}
Log.i(TAG, "switchCamera:" + facingId);
mMediaStreamingManager.switchCamera(facingId);
}
}
private class ScreenShooter implements Runnable {
@Override
public void run() {
final String fileName = "PLStreaming_" + System.currentTimeMillis() + ".jpg";
mMediaStreamingManager.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 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(AVStreamingActivity.this, info, Toast.LENGTH_LONG).show();
}
});
}
}
/**
* Accept only 32 bit png (ARGB)
* @return
*/
private WatermarkSetting buildWatermarkSetting() {
if (!mEncodingConfig.mIsWatermarkEnabled) {
return null;
}
WatermarkSetting watermarkSetting = new WatermarkSetting(this);
watermarkSetting.setResourceId(R.drawable.qiniu_logo);
watermarkSetting.setAlpha(mEncodingConfig.mWatermarkAlpha);
watermarkSetting.setSize(mEncodingConfig.mWatermarkSize);
if (mEncodingConfig.mIsWatermarkLocationPreset) {
watermarkSetting.setLocation(mEncodingConfig.mWatermarkLocationPreset);
} else {
watermarkSetting.setCustomPosition(mEncodingConfig.mWatermarkLocationCustomX, mEncodingConfig.mWatermarkLocationCustomY);
}
return watermarkSetting;
}
private CameraStreamingSetting buildCameraStreamingSetting() {
mCameraConfig = (CameraConfig) getIntent().getSerializableExtra("CameraConfig");
CameraStreamingSetting cameraStreamingSetting = new CameraStreamingSetting();
cameraStreamingSetting.setCameraId(mCameraConfig.mFrontFacing ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK)
.setCameraPrvSizeLevel(mCameraConfig.mSizeLevel)
.setCameraPrvSizeRatio(mCameraConfig.mSizeRatio)
.setFocusMode(mCameraConfig.mFocusMode)
.setContinuousFocusModeEnabled(mCameraConfig.mContinuousAutoFocus)
.setFrontCameraPreviewMirror(mCameraConfig.mPreviewMirror)
.setFrontCameraMirror(mCameraConfig.mEncodingMirror).setRecordingHint(false)
.setResetTouchFocusDelayInMs(3000)
.setBuiltInFaceBeautyEnabled(!mCameraConfig.mIsCustomFaceBeauty)
.setFaceBeautySetting(new CameraStreamingSetting.FaceBeautySetting(1.0f, 1.0f, 0.8f));
if (mCameraConfig.mIsFaceBeautyEnabled) {
cameraStreamingSetting.setVideoFilter(CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY);
} else {
cameraStreamingSetting.setVideoFilter(CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_NONE);
}
return cameraStreamingSetting;
}
@Override
public Camera.Size onPreviewSizeSelected(List list) {
/**
* You should choose a suitable size to avoid image scale
* eg: If streaming size is 1280 x 720, you should choose a camera preview size >= 1280 x 720
*/
Camera.Size size = null;
if (list != null) {
StreamingProfile.VideoEncodingSize encodingSize = mProfile.getVideoEncodingSize(mCameraConfig.mSizeRatio);
for (Camera.Size s : list) {
if (s.width >= encodingSize.width && s.height >= encodingSize.height) {
size = s;
Log.d(TAG, "selected size :" + size.width + "x" + size.height);
break;
}
}
}
return size;
}
@Override
public void initView() {
mCameraStreamingSetting = buildCameraStreamingSetting();
mIsEncOrientationPort = mEncodingConfig.mVideoOrientationPortrait;
mIsNeedFB = mCameraConfig.mIsFaceBeautyEnabled;
mIsPreviewMirror = mCameraConfig.mPreviewMirror;
mIsEncodingMirror = mCameraConfig.mEncodingMirror;
mCurrentCamFacingIndex = mCameraConfig.mFrontFacing ? 1 : 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
} else {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setRequestedOrientation(mIsEncOrientationPort ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setContentView(R.layout.activity_av_streaming);
mMuteButton = (Button) findViewById(R.id.mute_btn);
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);
Button previewMirrorBtn = (Button) findViewById(R.id.preview_mirror_btn);
Button encodingMirrorBtn = (Button) findViewById(R.id.encoding_mirror_btn);
mFaceBeautyBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsNeedFB = !mIsNeedFB;
mMediaStreamingManager.setVideoFilterType(mIsNeedFB ?
CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY
: CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_NONE);
updateFBButtonText();
}
});
mMuteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsNeedMute = !mIsNeedMute;
mMediaStreamingManager.mute(mIsNeedMute);
updateMuteButtonText();
}
});
previewMirrorBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsPreviewMirror = !mIsPreviewMirror;
mMediaStreamingManager.setPreviewMirror(mIsPreviewMirror);
Toast.makeText(AVStreamingActivity.this, "镜像成功", Toast.LENGTH_SHORT).show();
}
});
encodingMirrorBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsEncodingMirror = !mIsEncodingMirror;
mMediaStreamingManager.setEncodingMirror(mIsEncodingMirror);
Toast.makeText(AVStreamingActivity.this, "镜像成功", Toast.LENGTH_SHORT).show();
}
});
mTorchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
if (mIsTorchOn) {
mMediaStreamingManager.turnLightOff();
} else {
mMediaStreamingManager.turnLightOn();
}
mIsTorchOn = !mIsTorchOn;
setTorchEnabled(mIsTorchOn);
}
}).start();
}
});
mCameraSwitchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCameraSwitchBtn.removeCallbacks(mSwitcher);
mCameraSwitchBtn.postDelayed(mSwitcher, 100);
}
});
mCaptureFrameBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCaptureFrameBtn.removeCallbacks(mScreenShooter);
mCaptureFrameBtn.postDelayed(mScreenShooter, 100);
}
});
mEncodingOrientationSwitcherBtn = (Button) findViewById(R.id.orientation_btn);
mEncodingOrientationSwitcherBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mEncodingOrientationSwitcherBtn.removeCallbacks(mEncodingOrientationSwitcher);
mEncodingOrientationSwitcherBtn.postDelayed(mEncodingOrientationSwitcher, 100);
}
});
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;
mMediaStreamingManager.updateFaceBeautySetting(fbSetting);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
initButtonText();
}
private void initButtonText() {
updateFBButtonText();
updateCameraSwitcherButtonText(mCameraStreamingSetting.getReqCameraId());
mCaptureFrameBtn.setText("Capture");
updateFBButtonText();
updateMuteButtonText();
updateOrientationBtnText();
}
@Override
public void onStateChanged(StreamingState streamingState, Object extra) {
/**
* general states are handled in the `StreamingBaseActivity`
*/
super.onStateChanged(streamingState, extra);
switch (streamingState) {
case READY:
mMaxZoom = mMediaStreamingManager.getMaxZoom();
break;
case SHUTDOWN:
if (mOrientationChanged) {
mOrientationChanged = false;
startStreamingInternal();
}
break;
case OPEN_CAMERA_FAIL:
Log.e(TAG, "Open Camera Fail. id:" + extra);
break;
case CAMERA_SWITCHED:
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 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;
}
}
protected void setFocusAreaIndicator() {
if (mRotateLayout == null) {
mRotateLayout = (RotateLayout) findViewById(R.id.focus_indicator_rotate_layout);
mMediaStreamingManager.setFocusAreaIndicator(mRotateLayout,
mRotateLayout.findViewById(R.id.focus_indicator));
}
}
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);
}
});
}
private void updateOrientationBtnText() {
if (mIsEncOrientationPort) {
mEncodingOrientationSwitcherBtn.setText("Land");
} else {
mEncodingOrientationSwitcherBtn.setText("Port");
}
}
private void updateFBButtonText() {
if (mFaceBeautyBtn != null) {
mFaceBeautyBtn.setText(mIsNeedFB ? "FB Off" : "FB On");
}
}
private void updateMuteButtonText() {
if (mMuteButton != null) {
mMuteButton.setText(mIsNeedMute ? "Unmute" : "Mute");
}
}
private void updateCameraSwitcherButtonText(int camId) {
if (mCameraSwitchBtn == null) {
return;
}
if (camId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
mCameraSwitchBtn.setText("Back");
} else {
mCameraSwitchBtn.setText("Front");
}
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.i(TAG, "onSingleTapUp X:" + e.getX() + ",Y:" + e.getY());
if (mIsReady) {
setFocusAreaIndicator();
mMediaStreamingManager.doSingleTapUp((int) e.getX(), (int) e.getY());
return true;
}
return false;
}
@Override
public boolean onZoomValueChanged(float factor) {
if (mIsReady && mMediaStreamingManager.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);
mMediaStreamingManager.setZoomValue(mCurrentZoom);
}
return false;
}
@Override
public void onSurfaceCreated() {
Log.i(TAG, "onSurfaceCreated");
/**
* only used in custom beauty algorithm case
*/
mFBO.initialize(this);
}
@Override
public void onSurfaceChanged(int width, int height) {
Log.i(TAG, "onSurfaceChanged width:" + width + ",height:" + height);
/**
* only used in custom beauty algorithm case
*/
mFBO.updateSurfaceSize(width, height);
}
@Override
public void onSurfaceDestroyed() {
Log.i(TAG, "onSurfaceDestroyed");
/**
* only used in custom beauty algorithm case
*/
mFBO.release();
}
@Override
public int onDrawFrame(int texId, int texWidth, int texHeight, float[] transformMatrix) {
/**
* When using custom beauty algorithm, you should return a new texId from the SurfaceTexture.
* newTexId should not equal with texId, Otherwise, there is no filter effect.
*/
int newTexId = mFBO.drawFrame(texId, texWidth, texHeight);
return newTexId;
}
@Override
public boolean onPreviewFrame(byte[] bytes, int width, int height, int rotation, int fmt, long tsInNanoTime) {
Log.i(TAG, "onPreviewFrame " + width + "x" + height + ",fmt:" + (fmt == PLFourCC.FOURCC_I420 ? "I420" : "NV21") + ",ts:" + tsInNanoTime + ",rotation:" + rotation);
/**
* When using custom beauty algorithm in sw encode mode, you should change the bytes array's values here
* eg: byte[] beauties = readPixelsFromGPU();
* System.arraycopy(beauties, 0, bytes, 0, bytes.length);
*/
return true;
}
}