七牛推流SDK demo 中 关键类的注释


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});
    }
}




直播APP技术架构 直播APP涉及到如下几个方面的角色: 直播业务服务器,该服务器主要是验证客户端的权限并在权限合法之后授权客户端推流参数,客户端使用推流参数进行推流七牛直播系统,该系统主要根据直播业务服务器的请求来创建直播流,获取直播流信息,提取直播流回看地址等信息。 推流客户端,推流客户端主要工作是从直播业务服务器获取直播推流参数,然后将录制的视频流推送到七牛直播系统。 直播业务服务器一般由客户自行开发,用来和七牛直播系统进行交互,七牛提供服务端的SDK,客户可以很方便地使用适合自己的编程语言的SDK开发包来开发服务端API。 推流客户端一般由客户自行开发,用来和直播业务服务器交互,将视频流推送到七牛直播系统或者从直播业务服务器获取观看地址,然后从七牛直播系统根据地址获取视频内容。 直播APP业务流程 直播APP登录帐号,该账号的合法性和其相关的业务逻辑由直播业务服务器提供和验证。 直播APP从直播业务服务器获取推流的参数信息,准备使用集成在APP七牛推流SDK来将视频流推送到七牛直播系统 直播APP从直播业务服务器获取推流参数后,在开始推流时,发送开始信号给业务服务器,业务服务器记录下该直播过程的起始时间,并生成唯一性id给客户端 直播APP开始进行推流,推送的直播流数据将通过SDK直接发送到七牛直播系统,推流协议为RTMP。 其他的直播APP客户可以从直播业务服务器获取当前直播的RTMP或者HLS的地址进行观看,RTMP的实时性要优于HLS,另外七牛提供的直播播放器支持RTMP协议。 直播APP结束推流,同时发送停止推流信号给直播业务服务器,业务服务器记录下该直播过程的结束时间,可选性地让客户命名直播过程,方便未来回放。 直播APP本身也可以获取已直播完成的视频播放地址进行回看。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值