想要从底层一步步写起比较麻烦,需要了解一点图像处理的知识,为了快速开发,我选择通过第三方的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();
}
}
}