1. 程式人生 > >安卓 視訊直播二:推流端程式碼

安卓 視訊直播二:推流端程式碼

想要從底層一步步寫起比較麻煩,需要了解一點影象處理的知識,為了快速開發,我選擇通過第三方的SDK,這裡簡單說一下第三方SDK,其中有騰訊,阿里,百度雲,網易,金山雲,抖音,大牛都支援不過各有利弊。

(1)騰訊雲ILVB實名認證後需要人工稽核5個工作日,反正至今沒有看到SDK;

(2)阿里雲提供多媒體雲服務,但是至今尚未提供移動直播SDK;

(3)百度雲介面還很粗糙,連移動直播必選的美顏功能都不支援,首先淘汰;

(4)網易雲相比騰訊和阿里好一點,不過大部分功能受限制,文件比較老,而且會有推銷電話

(5)金山雲支援自定義音訊資料處理,可以把自己的噪聲抑制程式碼掛載進去;

(6)抖音偏重的是視訊處理,而不是直播

(7)大牛相比其他幾個算是最好用的,介面全面,文件清晰。

注:能完全執行起來的是金山雲、百度雲,大牛提供的SDK,而且除大牛外其他都需要賬號註冊。

SDK篩選方案:http://www.cctime.com/html/2016-6-6/1179900.htm

大牛Git:https://github.com/daniulive/SmarterStreaming

一、大牛相關文件

1. 說明文件

移動端SDK(V2)呼叫說明(更新於2018/10/21)

 Windows SDK說明(以C#為例)

windows/android/iOS播放器SDK(V2)Unity3D呼叫說明

2.Demo下載

 [Windows demo測試程式] Windows推送、播放、合成、導播、連麥Demo(64位)本地下載(更新於2018/10/16)

 [Android SDK demo工程程式碼] android推送、播放、轉發、一對一互動、後臺推攝像頭/螢幕Demo(V2介面,建議採用)(Android Studio工程)(更新於2018/10/18)

 [iOS SDK demo工程程式碼] iOS推送、播放、轉發、錄屏SDK(V2)本地下載(更新於2018/10/24)

二、android 編碼

1.系統要求

  • SDK 支援 Android 4.4 及以上版本;
  • 支援的 CPU 架構:armv7, arm64。

2.   準備工作

  • 確保 SmartPublisherJniV2.java 放到 com.daniulive.smartpublisher 包名下(可下載Demo,在其中找到);
  • Smartavengine.jar 加入到工程;
  • 拷貝 SmartPublisherV2\app\src\main\jniLibs\armeabi-v7a 和SmartPublisherV2\app\src\main\jniLibs\arm64-v8a 下 libSmartPublisher.so 到工程;

新增相關許可權:

<uses-permission  android :name=" "android.permission.WRITE_EXTERNAL_STORAGE" ></ uses-permission>
<uses-permission  android :name=" "android.permission.INTERNET" ></ uses-permission>
<uses-permission  android :name=" "android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission  android :name=" "android.permission.MODIFY_AUDIO_SETTINGS" />
  • Load 庫:
static {
    System.loadLibrary("SmartPublisher");
}
  • build.gradle 配置 32/64 位庫:
splits {
    abi {
        enable  true
        reset()

        // Specifies a list of ABIs that Gradle should create APKs for
        //include "armeabi"
        include , 'armeabi-v7a',  'arm64-v8a' //select ABIs to build APKs for
        // Specify that we do not want to also generate a universal APK that includes all ABIs
        universalApk  true
    }
}
  • 如需整合到自己系統測試,請用大牛直播 SDK 的 app name(不然整合提示 license failed),正式授權版按照授權 app name 正常使用即可:
  • 如何改 app-name:
    strings.xml 做以下修改:
    <string name="app_name">SmartPublisherSDKDemo</string>

3.SDK介面詳解

移動端SDK(V2)呼叫說明(更新於2018/10/21)

4.程式碼

XML

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.liang.beautifulphoto.Daniulive.CameraPushActivity">

    <SurfaceView
        android:id="@+id/cpush_sf_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/cpush_btn_switch"
        android:layout_width="55dp"
        android:layout_height="55dp"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintVertical_bias="0.03"
        android:layout_marginRight="15dp"
        android:background="@drawable/switch_facing_button_list"/>

    <Button
        android:id="@+id/cpush_btn_push"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="推流"
        android:textColor="@color/white"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintVertical_bias="0.8"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.05"
        android:background="#50000000" />

    <Button
        android:id="@+id/cpush_btn_url"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="輸入推流URL"
        android:textColor="@color/white"
        app:layout_constraintLeft_toRightOf="@id/cpush_btn_push"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintTop_toTopOf="@id/cpush_btn_push"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="10dp"
        android:background="#50000000"/>

    <TextView
        android:id="@+id/cpush_tv_url"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="URL:rtmp://player.daniulive.com:1935/hls/stream"
        android:textColor="@color/white"
        android:textAllCaps="false"
        android:layout_marginTop="10dp"
        app:layout_constraintLeft_toLeftOf="@id/cpush_btn_push"
        app:layout_constraintTop_toBottomOf="@id/cpush_btn_push"/>
</android.support.constraint.ConstraintLayout>

activity:

package com.liang.beautifulphoto.Daniulive;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.daniulive.smartpublisher.SmartPublisherJniV2;
import com.daniulive.smartpublisher.SmartPublisherJniV2.WATERMARK;
import com.eventhandle.NTSmartEventCallbackV2;
import com.eventhandle.NTSmartEventID;
import com.liang.beautifulphoto.R;
import com.voiceengine.NTAudioRecordV2;
import com.voiceengine.NTAudioRecordV2Callback;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import superClass.superActivity;
import superInterface.myInterface;

public class CameraPushActivity extends superActivity implements myInterface, Callback, Camera.PreviewCallback {

    private static String TAG = "LIANG-CameraPushActivity";

    /* 推流解析度選擇
     * 0: 640*480
     * 1: 320*240
     * 2: 176*144
     * 3: 1280*720
     * */
    private int videoWidth = 1280;
    private int videoHight = 720;

    /* 推送型別選擇
     * 0: 音視訊
     * 1: 純音訊
     * 2: 純視訊
     * */
    //private Spinner pushTypeSelector;
    private int pushType = 0;

    /* 水印型別選擇
     * 0: 圖片水印
     * 1: 全部水印
     * 2: 文字水印
     * 3: 不加水印
     * */
    //private Spinner watermarkSelctor;
    private int watemarkType = 3;

    /* video軟編碼profile設定
     * 1: baseline profile
     * 2: main profile
     * 3: high profile
     * */
    //private Spinner swVideoEncoderProfileSelector;
    private int sw_video_encoder_profile = 1;    //default with baseline profile

    //編碼速度
    private int sw_video_encoder_speed = 6;

    private Button   switchFaceButton;
    private Button   pushButton;
    private Button   urlButon;
    private TextView curUrlTView;

    private SurfaceView   mSurfaceView   = null;
    private SurfaceHolder mSurfaceHolder = null;

    private Camera                   mCamera             = null;
    private Camera.AutoFocusCallback myAutoFocusCallback = null;

    //Daniu
    private SmartPublisherJniV2 libPublisher = null;
    NTAudioRecordV2         audioRecord_         = null;
    NTAudioRecordV2Callback audioRecordCallback_ = null;

    //標誌
    private static final int     FRONT                   = 1;               //前置攝像頭標記
    private static final int     BACK                    = 2;                 //後置攝像頭標記
    private              int     currentCameraType       = BACK;             //當前開啟的攝像頭標記
    private              int     curCameraIndex          = -1;                   //當前攝像頭
    private              boolean mPreviewRunning         = false;         //正在錄製標誌
    private static final int     PORTRAIT                = 1;    //豎屏
    private static final int     LANDSCAPE               = 2;    //橫屏 home鍵在右邊的情況
    private static final int     LANDSCAPE_LEFT_HOME_KEY = 3; // 橫屏 home鍵在左邊的情況
    private              int     currentOrigentation     = PORTRAIT;
    private              boolean is_speex                = true;                  //啟用語音引擎
    private              boolean is_noise_suppression    = true;    //降噪處理
    private              boolean is_agc                  = false;                   //AGC自動發電量控制

    private boolean isStart     = false;
    private boolean isPushing   = false;
    private boolean isRecording = false;

    private boolean is_hardware_encoder = false;

    //字串
    private String publishURL;
    final private String baseURL      = "rtmp://player.daniulive.com:1935/hls/stream";
    private       String inputPushURL = "";
    private       String printText    = "URL:";
    private       String curState     = "當前狀態";

    //水印
    final private String  logoPath               = "/sdcard/daniulivelogo.png";
    private       boolean isWritelogoFileSuccess = false;

    private long publisherHandle = 0;
    private int  frameCount      = 0;

    private Context myContext;

    //Load 庫
    static {
        System.loadLibrary("SmartPublisher");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //螢幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_camera_push);

        myContext = this.getApplicationContext();
        getPermission();
        initView();
        initEvent();

    }

    @Override
    public void initView() {
        switchFaceButton = (Button) findViewById(R.id.cpush_btn_switch);
        pushButton = (Button) findViewById(R.id.cpush_btn_push);
        urlButon = (Button) findViewById(R.id.cpush_btn_url);
        curUrlTView = (TextView) findViewById(R.id.cpush_tv_url);

        mSurfaceView = (SurfaceView) findViewById(R.id.cpush_sf_preview);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(this);
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        //自動聚焦變量回調
        myAutoFocusCallback = new Camera.AutoFocusCallback() {
            public void onAutoFocus(boolean success, Camera camera) {
                if (success)//success表示對焦成功
                {
                    Log.e(TAG, "onAutoFocus succeed...");
                } else {
                    Log.e(TAG, "onAutoFocus failed...");
                }
            }
        };

        libPublisher = new SmartPublisherJniV2();
    }

    @Override
    public void initEvent() {
        switchFaceButton.setOnClickListener(new SwitchCameraListener());
        pushButton.setOnClickListener(new ButtonStartPushListener());
        urlButon.setOnClickListener(new ButtonInputPushUrlListener());
    }

    /**
     * 動態許可權,安卓6.0後申請動態許可權
     */
    private void getPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            //申請執行時許可權
            List<String> permissionList = new ArrayList<>();
            if (ContextCompat.checkSelfPermission(CameraPushActivity.this, Manifest.permission.RECORD_AUDIO)
                    != PackageManager.PERMISSION_GRANTED) {
                //錄製聲音的許可權
                permissionList.add(Manifest.permission.RECORD_AUDIO);
            }
            if (ContextCompat.checkSelfPermission(CameraPushActivity.this, Manifest.permission.CAMERA)
                    != PackageManager.PERMISSION_GRANTED) {
                //使用攝像頭的許可權
                permissionList.add(Manifest.permission.CAMERA);
            }
            if (ContextCompat.checkSelfPermission(CameraPushActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                //外部儲存器的許可權
                permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }

            if (!permissionList.isEmpty()) {
                String[] permisssion = permissionList.toArray(new String[permissionList.size()]);
                ActivityCompat.requestPermissions(this, permisssion, 1);//申請
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0) {
                    for (int result : grantResults) {
                        if (result != PackageManager.PERMISSION_GRANTED) {
                            Toast.makeText(this, "必須同意所有許可權才可使用本程式", Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }
                } else {
                    Toast.makeText(this, "發生未知錯誤", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        frameCount++;
        if (frameCount % 3000 == 0) {
            Log.i("OnPre", "gc+");
            System.gc();
            Log.i("OnPre", "gc-");
        }

        if (data == null) {
            Camera.Parameters params = camera.getParameters();
            Camera.Size size = params.getPreviewSize();
            int bufferSize = (((size.width | 0x1f) + 1) * size.height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;
            camera.addCallbackBuffer(new byte[bufferSize]);
        } else {
            if (isStart || isPushing || isRecording) {
                libPublisher.SmartPublisherOnCaptureVideoData(publisherHandle, data, data.length, currentCameraType, currentOrigentation);
            }

            camera.addCallbackBuffer(data);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.e(TAG, "surfaceCreated: ");
        try {

            int CammeraIndex = findBackCamera();
            Log.e(TAG, "BackCamera: " + CammeraIndex);

            if (CammeraIndex == -1) {
                CammeraIndex = findFrontCamera();
                currentCameraType = FRONT;
                switchFaceButton.setEnabled(false);
                if (CammeraIndex == -1) {
                    Log.e(TAG, "NO camera!!");
                    return;
                }
            } else {
                currentCameraType = BACK;
            }

            if (mCamera == null) {
                mCamera = openCamera(currentCameraType);
            }

        } catch (Exception e) {
            //e.printStackTrace();
            Log.e(TAG, "surfaceCreated: Exception");
        }

    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.e(TAG, "surfaceChanged: ");
        initCamera(surfaceHolder);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

    }

    /*****************自定義方法******************/
    /*it will call when surfaceChanged*/
    private void initCamera(SurfaceHolder holder) {
        Log.e(TAG, "initCamera..");

        if (mPreviewRunning) {
            mCamera.stopPreview();
        }

        Camera.Parameters parameters;
        try {
            parameters = mCamera.getParameters();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return;
        }

        parameters.setPreviewSize(videoWidth, videoHight);
        parameters.setPictureFormat(PixelFormat.JPEG);
        parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);

        SetCameraFPS(parameters);

        setCameraDisplayOrientation(this, curCameraIndex, mCamera);

        mCamera.setParameters(parameters);

        int bufferSize = (((videoWidth | 0xf) + 1) * videoHight * ImageFormat.getBitsPerPixel(parameters.getPreviewFormat())) / 8;

        mCamera.addCallbackBuffer(new byte[bufferSize]);

        mCamera.setPreviewCallbackWithBuffer(this);
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (Exception ex) {
            // TODO Auto-generated catch block
            if (null != mCamera) {
                mCamera.release();
                mCamera = null;
            }
            ex.printStackTrace();
        }
        mCamera.startPreview();
        mCamera.autoFocus(myAutoFocusCallback);
        mPreviewRunning = true;
    }

    //檢查它是否有後置攝像頭
    private int findBackCamera() {
        int cameraCount = 0;
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        cameraCount = Camera.getNumberOfCameras();

        for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
            Camera.getCameraInfo(camIdx, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                return camIdx;
            }
        }
        return -1;
    }

    //檢查它是否有前攝像頭
    private int findFrontCamera() {
        int cameraCount = 0;
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        cameraCount = Camera.getNumberOfCameras();

        for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
            Camera.getCameraInfo(camIdx, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                return camIdx;
            }
        }
        return -1;
    }

    //開啟攝像頭
    @SuppressLint("NewApi")
    private Camera openCamera(int type) {
        int frontIndex = -1;
        int backIndex = -1;
        int cameraCount = Camera.getNumberOfCameras();
        Log.e(TAG, "cameraCount: " + cameraCount);

        Camera.CameraInfo info = new Camera.CameraInfo();
        for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
            Camera.getCameraInfo(cameraIndex, info);

            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                frontIndex = cameraIndex;
            } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                backIndex = cameraIndex;
            }
        }

        currentCameraType = type;
        if (type == FRONT && frontIndex != -1) {
            curCameraIndex = frontIndex;
            return Camera.open(frontIndex);
        } else if (type == BACK && backIndex != -1) {
            curCameraIndex = backIndex;
            return Camera.open(backIndex);
        }
        return null;
    }

    //切換攝像頭
    private void switchCamera() throws IOException {
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
        if (currentCameraType == FRONT) {
            mCamera = openCamera(BACK);
        } else if (currentCameraType == BACK) {
            mCamera = openCamera(FRONT);
        }

        initCamera(mSurfaceHolder);
    }

    //設定FPS
    private void SetCameraFPS(Camera.Parameters parameters) {
        if (parameters == null) {
            return;
        }

        int[] findRange = null;

        int defFPS = 20 * 1000;

        List<int[]> fpsList = parameters.getSupportedPreviewFpsRange();
        if (fpsList != null && fpsList.size() > 0) {
            for (int i = 0; i < fpsList.size(); ++i) {
                int[] range = fpsList.get(i);
                if (range != null
                        && Camera.Parameters.PREVIEW_FPS_MIN_INDEX < range.length
                        && Camera.Parameters.PREVIEW_FPS_MAX_INDEX < range.length) {
                    Log.e(TAG, "Camera index:" + i + " support min fps:" + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]);

                    Log.e(TAG, "Camera index:" + i + " support max fps:" + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);

                    if (findRange == null) {
                        if (defFPS <= range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
                            findRange = range;

                            Log.e(TAG, "Camera found appropriate fps, min fps:" + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]
                                    + " ,max fps:" + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
                        }
                    }
                }
            }
        }
    }

    //設定方向
    private void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) {
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }

        Log.e(TAG, "curDegree: " + result);

        camera.setDisplayOrientation(result);
    }

    private void stopPush() {
        if (!isRecording) {
            if (audioRecord_ != null) {
                Log.e(TAG, "stopPush, call audioRecord_.StopRecording..");


                audioRecord_.Stop();

                if (audioRecordCallback_ != null) {
                    audioRecord_.RemoveCallback(audioRecordCallback_);
                    audioRecordCallback_ = null;
                }

                audioRecord_ = null;

                //audioRecord_.StopRecording();
                //audioRecord_ = null;
            }
        }

        if (libPublisher != null) {
            libPublisher.SmartPublisherStopPublisher(publisherHandle);
        }

        if (!isRecording) {
            if (publisherHandle != 0) {
                if (libPublisher != null) {
                    libPublisher.SmartPublisherClose(publisherHandle);
                    publisherHandle = 0;
                }
            }
        }

    }

    private void ConfigControlEnable(boolean isEnable) {
    }

    //這裡硬編碼位元速率是按照25幀來計算的
    private int setHardwareEncoderKbps(int width, int height) {
        int hwEncoderKpbs = 0;

        int area = width * height;

        if (area < (200 * 180)) {
            hwEncoderKpbs = 300;
        } else if (area < (400 * 320)) {
            hwEncoderKpbs = 600;
        } else if (area < (640 * 500)) {
            hwEncoderKpbs = 1200;
        } else if (area < (960 * 600)) {
            hwEncoderKpbs = 1500;
        } else if (area < (1300 * 720)) {
            hwEncoderKpbs = 2000;
        } else if (area < (2000 * 1080)) {
            hwEncoderKpbs = 3000;
        } else {
            hwEncoderKpbs = 4000;
        }

        return hwEncoderKpbs;
    }

    private void InitAndSetConfig() {
        Log.e(TAG, "videoWidth: " + videoWidth + " videoHight: " + videoHight
                + " pushType:" + pushType);

        int audio_opt = 1;
        int video_opt = 1;

        if (pushType == 1) {
            video_opt = 0;
        } else if (pushType == 2) {
            audio_opt = 0;
        }

        publisherHandle = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt,
                videoWidth, videoHight);

        if (publisherHandle == 0) {
            Log.e(TAG, "sdk open failed!");
            return;
        }

        Log.e(TAG, "publisherHandle=" + publisherHandle);

        if (is_hardware_encoder) {
            int hwHWKbps = setHardwareEncoderKbps(videoWidth, videoHight);

            Log.e(TAG, "hwHWKbps: " + hwHWKbps);

            int isSupportHWEncoder = libPublisher
                    .SetSmartPublisherVideoHWEncoder(publisherHandle, hwHWKbps);

            if (isSupportHWEncoder == 0) {
                Log.e(TAG, "Great, it supports hardware encoder!");
            }
        }

        libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandeV2());

        // 如果想和時間顯示在同一行,請去掉'\n'
        String watermarkText = "大牛直播(daniulive)\n\n";


        String path = logoPath;

        if (watemarkType == 0) {
            if (isWritelogoFileSuccess) {
                libPublisher.SmartPublisherSetPictureWatermark(publisherHandle, path,
                        WATERMARK.WATERMARK_POSITION_TOPRIGHT, 160,
                        160, 10, 10);
            }

        } else if (watemarkType == 1) {
            if (isWritelogoFileSuccess) {
                libPublisher.SmartPublisherSetPictureWatermark(publisherHandle, path,
                        WATERMARK.WATERMARK_POSITION_TOPRIGHT, 160,
                        160, 10, 10);
            }

            libPublisher.SmartPublisherSetTextWatermark(publisherHandle, watermarkText, 1,
                    WATERMARK.WATERMARK_FONTSIZE_BIG,
                    WATERMARK.WATERMARK_POSITION_BOTTOMRIGHT, 10, 10);

            // libPublisher.SmartPublisherSetTextWatermarkFontFileName("/system/fonts/DroidSansFallback.ttf");

            // libPublisher.SmartPublisherSetTextWatermarkFontFileName("/sdcard/DroidSansFallback.ttf");
        } else if (watemarkType == 2) {
            libPublisher.SmartPublisherSetTextWatermark(publisherHandle, watermarkText, 1,
                    WATERMARK.WATERMARK_FONTSIZE_BIG,
                    WATERMARK.WATERMARK_POSITION_BOTTOMRIGHT, 10, 10);

            // libPublisher.SmartPublisherSetTextWatermarkFontFileName("/system/fonts/DroidSansFallback.ttf");
        } else {
            Log.e(TAG, "no watermark settings..");
        }
        // end

        if (!is_speex) {
            // set AAC encoder
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);
        } else {
            // set Speex encoder
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);
            libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);
        }

        libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1
                : 0);

        libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);

        // libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);

        libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, sw_video_encoder_profile);

        libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);

        // libPublisher.SetRtmpPublishingType(publisherHandle, 0);

        // libPublisher.SmartPublisherSetGopInterval(publisherHandle, 40);

        // libPublisher.SmartPublisherSetFPS(publisherHandle, 15);

        // libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 600, 1200);

        libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);

    }

    void CheckInitAudioRecorder() {
        if (audioRecord_ == null) {
            //audioRecord_ = new NTAudioRecord(this, 1);

            audioRecord_ = new NTAudioRecordV2(this);
        }

        if (audioRecord_ != null) {
            Log.e(TAG, "CheckInitAudioRecorder call audioRecord_.start()+++...");

            audioRecordCallback_ = new NTAudioRecordV2CallbackImpl();

            audioRecord_.AddCallback(audioRecordCallback_);

            audioRecord_.Start();

            Log.e(TAG, "CheckInitAudioRecorder call audioRecord_.start()---...");


            //Log.e(TAG, "onCreate, call executeAudioRecordMethod..");
            // auido_ret: 0 ok, other failed
            //int auido_ret= audioRecord_.executeAudioRecordMethod();
            //Log.e(TAG, "onCreate, call executeAudioRecordMethod.. auido_ret=" + auido_ret);
        }
    }

    private void PopInputUrlDialog() {
        final EditText inputUrlTxt = new EditText(this);
        inputUrlTxt.setFocusable(true);
        inputUrlTxt.setText(baseURL + String.valueOf((int) (System.currentTimeMillis() % 1000000)));

        AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);
        builderUrl.setTitle("如 rtmp://player.daniulive.com:1935/hls/stream123456").setView(inputUrlTxt).setNegativeButton(
                "取消", null);

        builderUrl.setPositiveButton("確認", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                String fullPushUrl = inputUrlTxt.getText().toString();
                SaveInputUrl(fullPushUrl);
            }
        });

        builderUrl.show();
    }

    private void SaveInputUrl(String url) {
        inputPushURL = "";

        if (url == null) {
            return;
        }

        // rtmp://
        if (url.length() < 8) {
            Log.e(TAG, "Input publish url error:" + url);
            return;
        }

        if (!url.startsWith("rtmp://")) {
            Log.e(TAG, "Input publish url error:" + url);
            return;
        }

        inputPushURL = url;

        Log.e(TAG, "Input publish url:" + url);
    }

    /***************內部類*********************/
    //切換攝像頭
    class SwitchCameraListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "Switch camera..");
            try {
                switchCamera();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    //推流
    class ButtonStartPushListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            if (isStart) {
                return;
            }

            if (isPushing) {
                stopPush();

                if (!isRecording) {
                    ConfigControlEnable(true);
                }

                pushButton.setText(" 推送");

                isPushing = false;

                return;
            }

            Log.e(TAG, "onClick start push..");

            if (libPublisher == null) {
                return;
            }

            isPushing = true;

            if (!isRecording) {
                InitAndSetConfig();
            }

            if (inputPushURL != null && inputPushURL.length() > 1) {
                publishURL = inputPushURL;
                Log.e(TAG, "start, input publish url:" + publishURL);
            } else {
                publishURL = baseURL + String.valueOf((int) (System.currentTimeMillis() % 1000000));
                Log.e(TAG, "start, generate random url:" + publishURL);
            }

            printText = "URL:" + publishURL;
            Log.e(TAG, printText);

            if (libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0) {
                Log.e(TAG, "Failed to set publish stream URL..");
            }

            int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
            if (startRet != 0) {
                isPushing = false;

                Log.e(TAG, "Failed to start push stream..");
                return;
            }

            if (!isRecording) {
                if (pushType == 0 || pushType == 1) {
                    CheckInitAudioRecorder();    //enable pure video publisher..
                }
            }

            if (!isRecording) {
                ConfigControlEnable(false);
            }

            curUrlTView = (TextView) findViewById(R.id.cpush_tv_url);
            curUrlTView.setText(printText);

            pushButton.setText(" 停止推送 ");

        }

    }

    //狀態
    class EventHandeV2 implements NTSmartEventCallbackV2 {
        @Override
        public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {

            Log.e(TAG, "EventHandeV2: handle=" + handle + " id:" + id);

            switch (id) {
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:
                    curState = "開始。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
                    curState = "連線中。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
                    curState = "連線失敗。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
                    curState = "連線成功。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
                    curState = "連線斷開。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
                    curState = "關閉。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
                    Log.e(TAG, "開始一個新的錄影檔案 : " + param3);
                    curState = "開始一個新的錄影檔案。。";
                    break;
                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
                    Log.e(TAG, "已生成一個錄影檔案 : " + param3);
                    curState = "已生成一個錄影檔案。。";
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
                    Log.e(TAG, "傳送時延: " + param1 + " 幀數:" + param2);
                    curState = "收到傳送時延..";
                    break;

                case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
                    Log.e(TAG, "快照: " + param1 + " 路徑:" + param3);

                    if (param1 == 0) {
                        curState = "擷取快照成功。.";
                    } else {
                        curState = "擷取快照失敗。.";
                    }
                    break;
                default:
            }

            String str = "當前回撥狀態:" + curState;

            Log.e(TAG, str);

        }
    }

    class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback {
        @Override
        public void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number) {
    		 /*
    		 Log.e(TAG, "onNTAudioRecordV2Frame size=" + size + " sampleRate=" + sampleRate + " channel=" + channel
    				 + " per_channel_sample_number=" + per_channel_sample_number);

    		 */

            if (publisherHandle != 0) {
                libPublisher.SmartPublisherOnPCMData(publisherHandle, data, size, sampleRate, channel, per_channel_sample_number);
            }
        }
    }

    //推流URL
    class ButtonInputPushUrlListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            PopInputUrlDialog();
        }
    }

}