安卓 視訊直播二:推流端程式碼
想要從底層一步步寫起比較麻煩,需要了解一點影象處理的知識,為了快速開發,我選擇通過第三方的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. 說明文件
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介面詳解
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();
}
}
}