From: Peng Li Date: Wed, 23 May 2018 08:54:09 +0000 (+0800) Subject: Merge branch 'dev' v0.3.0 X-Git-Tag: v0.3.2~1 X-Git-Url: http://47.100.26.94:8080/?a=commitdiff_plain;h=refs%2Ftags%2Fv0.3.0;hp=4550fc273633f4e985cdd8c2f3d7a324887dac9e;p=rtmpclient.git Merge branch 'dev' v0.3.0 1. handle Network event 2. handle USB event 3. fix backpress issue 4. Log improvement --- diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b255af6..6c2a7f8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,7 @@ + diff --git a/app/src/main/java/ai/suanzi/rtmpclient/CameraView.java b/app/src/main/java/ai/suanzi/rtmpclient/CameraView.java deleted file mode 100644 index ab3e145..0000000 --- a/app/src/main/java/ai/suanzi/rtmpclient/CameraView.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.suanzi.rtmpclient; - -import android.os.Parcelable; -import android.view.SurfaceView; -import android.os.Parcel; -import android.content.Context; -import android.view.SurfaceHolder; - - -public class CameraView extends SurfaceView implements Parcelable { - - private SurfaceHolder mHolder; - - public CameraView(Context context) { - super(context); - mHolder = getHolder(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java b/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java new file mode 100644 index 0000000..ad69bf9 --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/FfmpegHelper.java @@ -0,0 +1,37 @@ +package ai.suanzi.rtmpclient; + +import org.apache.log4j.Logger; + +public class FfmpegHelper { + + static { + String arch = System.getProperty("os.arch"); + if (arch.equals("i686")){ + System.loadLibrary("x264"); + } + System.loadLibrary("avutil"); + System.loadLibrary("postproc"); + System.loadLibrary("swresample"); + System.loadLibrary("swscale"); + System.loadLibrary("avcodec"); + System.loadLibrary("avdevice"); + System.loadLibrary("avfilter"); + System.loadLibrary("avformat"); + System.loadLibrary("FfmpegHelperJNI"); + } + + private static Logger gLogger = Logger.getLogger("FfmpegHelper"); + + // callback from native + public static void javaPrint(String string, int level){ + if(level == 1) + gLogger.error(string); + else + gLogger.info(string); + } + + // native methods + public static native int initEncoder(int width, int height, String url); + public static native int processFrame(byte[] frame); + public static native int close(); +} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/JniLog.java b/app/src/main/java/ai/suanzi/rtmpclient/JniLog.java deleted file mode 100644 index 728c59e..0000000 --- a/app/src/main/java/ai/suanzi/rtmpclient/JniLog.java +++ /dev/null @@ -1,5 +0,0 @@ -package ai.suanzi.rtmpclient; - -public class JniLog { - -} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/MainActivity.java b/app/src/main/java/ai/suanzi/rtmpclient/MainActivity.java index 4886501..16322c6 100644 --- a/app/src/main/java/ai/suanzi/rtmpclient/MainActivity.java +++ b/app/src/main/java/ai/suanzi/rtmpclient/MainActivity.java @@ -1,42 +1,19 @@ package ai.suanzi.rtmpclient; -import android.app.Activity; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.IntentFilter; -import android.graphics.ImageFormat; -import android.graphics.SurfaceTexture; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.os.Environment; import android.support.design.widget.TextInputEditText; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; -import android.text.TextUtils; import android.util.Log; import android.view.SurfaceHolder; -import android.hardware.Camera; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.Toast; import android.content.Context; -import android.content.pm.PackageManager; -import android.os.AsyncTask; import java.io.File; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.io.IOException; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import android.content.Intent; -import java.io.OutputStream; -import android.hardware.usb.UsbManager; -import android.hardware.usb.UsbDeviceConnection; - import de.mindpipe.android.logging.log4j.LogConfigurator; import org.apache.log4j.Level; import org.apache.log4j.Logger; @@ -47,36 +24,28 @@ import android.content.ComponentName; import ai.suanzi.rtmpclient.MyService.LocalBinder; import android.os.IBinder; +import android.net.ConnectivityManager; -//"rtmp://gpussh.suanzi.ai:1935/myapp/suanzi_ac83f34ead90_cameraid"; - -public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PreviewCallback{ - - private static final String TAG = "PENG"; - //private Ffmpeg ffmpeg; - //private Camera mCamera ; - //private StreamTask mStreamTask; - private SurfaceHolder mHolder; - private SurfaceView mSufaceView; - //private UVCCamera uvcCamera; - //ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - //Intent it = new Intent(getApplicationContext(), MyService.class); - Intent intent = new Intent(); +//"rtmp://gpussh.suanzi.ai:1935/myapp/suanzi_ac83f34ead90"; - //private UsbManager usbManager; - //private UsbDevice usbCamera; +public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, MyService.MyServiceEventListener{ + private static final String TAG = "MainActivity"; private Logger gLogger; + private SurfaceHolder mHolder; + private SurfaceView mSufaceView; private Button mBtnStart; private TextInputEditText mTextServer; private TextInputEditText mTextUser; private TextInputEditText mTextCamera; private String mMacAddr = ""; - //private CameraView mCameraView; - private String mRtmpUrl; + private NetworkMonitor networkMonitor; + private UsbMonitor mUsbMonitor; + private ServiceHealthMonitor mServiceHealthMonitor; + private static final int INTERVAL = 3 * 60; // seconds - boolean mBounded; + boolean mBounded = false; MyService mServer; Intent mIntent; @@ -111,9 +80,64 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal mHolder = mSufaceView.getHolder(); mHolder.addCallback(this); + mIntent = new Intent(this, MyService.class); + mUsbMonitor = new UsbMonitor(new UsbMonitor.UsbListener() { + @Override + public void onCameraConnected() { + gLogger.error("onCameraConnected, current Usb Camera count: " + mUsbMonitor.getUsbCameraCount()); + doUnbindService(); + if(mUsbMonitor.hasUsbCamera()){ + doBindService(); + } + } + + @Override + public void onCameraDisconnected() { + gLogger.error("onCameraDisconnected, current camera count: " + mUsbMonitor.getUsbCameraCount()); + doUnbindService(); + if(mUsbMonitor.hasUsbCamera()){ + doBindService(); + } + } + }, this); + + networkMonitor = new NetworkMonitor(new NetworkMonitor.NetworkListener() { + @Override + public void onWifiConnected() { + gLogger.error("onWifiConnected"); + doBindService(); + } + + @Override + public void onWifiDisconnected() { + gLogger.error("onWifiDisconnected"); + doUnbindService(); + } + + @Override + public void onWifiEnabled() { + gLogger.error("onWifiEnabled"); + } + + @Override + public void onWifiDisabled() { + gLogger.error("onWifiDisabled"); + } + }); + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + registerReceiver(networkMonitor, filter); - //intent.setPackage(this.getPackageName()); // init service - //intent.setAction("ai.suanzi.rtmpclient.service"); + mServiceHealthMonitor = new ServiceHealthMonitor(new ServiceHealthMonitor.Callback() { + @Override + public void onNotHealthy() { + gLogger.error("onNotHealthy, in " + INTERVAL + " seconds, the publishing may stopped or have error "); + doUnbindService(); + doBindService(); + //restartApplication(); + } + }); + mServiceHealthMonitor.setInterval(INTERVAL); // 5 minutes } @@ -133,13 +157,32 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal mBounded = true; LocalBinder mLocalBinder = (LocalBinder)service; mServer = mLocalBinder.getServiceInstance(); - if(mServer.setRtmpUrl(UserInfo.getConfig().toUrl())){ + mServer.setServiceEventListener(MainActivity.this); + if(mServer.setRtmpUrl(UserInfo.getConfig().toUrl(mMacAddr))){ mServer.startPreview(mHolder); } + } }; + private void doBindService(){ + gLogger.debug("doBindService"); + if(!mBounded && canStartService()) { + gLogger.debug("Start service --------->"); + bindService(mIntent, mConnection, BIND_AUTO_CREATE); + } + } + + private void doUnbindService() { + gLogger.debug("doUnbindService"); + if(mBounded){ + gLogger.debug("Stop service <---------"); + unbindService(mConnection); + mBounded = false; + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -148,10 +191,12 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal init(); loadConfig(); - //startService(intent); - mIntent = new Intent(this, MyService.class); - bindService(mIntent, mConnection, BIND_AUTO_CREATE); - + if(NetworkMonitor.isNetworkAvailable(this) && mUsbMonitor.hasUsbCamera()){ + gLogger.error("Current network is available"); + doBindService(); + } else { + gLogger.error("Current network NOT available or no USB Camera connected"); + } mBtnStart.setText("start"); mBtnStart.setOnClickListener(new View.OnClickListener(){ @@ -159,10 +204,15 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal public void onClick(View view){ gLogger.error("----------> onClick"); saveConfig(); - unbindService(mConnection); - bindService(mIntent, mConnection, BIND_AUTO_CREATE); + doUnbindService(); + doBindService(); } }); + + if(!mServiceHealthMonitor.isAlive()) { + gLogger.debug("mServiceHealthMonitor start, interval " + INTERVAL); + mServiceHealthMonitor.start(); + } } @Override @@ -192,6 +242,9 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal @Override protected void onDestroy(){ super.onDestroy(); + mUsbMonitor.unregisterReceiver(); + unregisterReceiver(networkMonitor); + unbindService(mConnection); gLogger.debug("onDestroy --------->"); } @@ -201,8 +254,14 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal gLogger.debug("onRestart ---------->"); } - - + @Override + public void onBackPressed() { + gLogger.error("onBackPressed --------->"); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } // SurfaceHolder.Callback implementation @Override @@ -223,13 +282,6 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal public void surfaceDestroyed(SurfaceHolder holder){ gLogger.debug("surfaceDestroyed"); } - @Override - public void onPreviewFrame(final byte[] data, Camera camera){ - gLogger.error("onPreviewFrame"); - - } - - private String getMacAddr() { WifiManager manager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); WifiInfo info = manager.getConnectionInfo(); @@ -243,7 +295,7 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal mTextUser.setText(info.user); mTextCamera.setText(info.cameraId); //mMacAddr = info.macAddr.equals("") ? this.mMacAddr : info.macAddr; - gLogger.error("loadConfig - url is :" + info.toUrl()); + gLogger.error("loadConfig - url is :" + info.toUrl(mMacAddr)); } private void saveConfig() { @@ -254,124 +306,36 @@ public class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal } else { Toast.makeText(getApplicationContext(), "Error: config saved", Toast.LENGTH_LONG).show(); } - gLogger.error("saveConfig - url: " + info.toUrl()); - } - - private void changePermission(){ - try { - Log.e(TAG, "change permission"); - //Process sh = Runtime.getRuntime().exec(new String[]{"su", "-c", "chmod 666 /dev/video0"}); - - Process sh = Runtime.getRuntime().exec("/system/xbin/su", null,null); - //Process sh = Runtime.getRuntime().exec("su", null,null); - - OutputStream os = sh.getOutputStream(); - os.write(("/system/bin/chmod 666 /dev/video0").getBytes("ASCII")); - //os.write(("/system/bin/echo 'wowo' >> /data/local/test").getBytes("ASCII")); - os.flush(); - os.close(); - sh.waitFor(); - - - }catch (Exception e){ - e.printStackTrace(); - } + gLogger.error("saveConfig - url: " + info.toUrl(mMacAddr)); } - private boolean checkCameraHardware(Context context) { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); + private boolean canStartService(){ + return mUsbMonitor.hasUsbCamera() && NetworkMonitor.isNetworkAvailable(this); } - private static Camera getCameraInstance(){ - Camera c = null; - try { - Log.e(TAG, "Open Camera"); - c = Camera.open(1); - } catch (Exception e){ - e.printStackTrace(); + // MyServiceEventListener + @Override + public void onCameraError(String msg){ + gLogger.error("onCameraEvent " + msg); + if(mUsbMonitor.hasUsbCamera()){ + //mServer.reopenCamera(); } - return c; } - private void configCamera(Camera camera){ - Camera.Parameters paras = camera.getParameters(); - gLogger.error("Supported Picture Sizes:"); - for (Camera.Size cc : paras.getSupportedPictureSizes()){ - gLogger.error(cc.width + "x" + cc.height); - } - gLogger.error("Supported Preview fps range:"); - for(int[] i : paras.getSupportedPreviewFpsRange()){ - gLogger.error("[" + i[0] + "," + i[1] + "]"); - } - gLogger.error("Set parameters"); - camera.setParameters(paras); - camera.setDisplayOrientation(0); - gLogger.error("Preview Format: " + paras.getPreviewFormat() + ". Size: " + paras.getPreviewSize().width + "x" + paras.getPreviewSize().height); - gLogger.error("Picture Format: " + paras.getPictureFormat() + ". Size: " + paras.getPictureSize().width + "x" + paras.getPictureSize().height); - - try { - camera.setPreviewDisplay(mHolder); - } catch (IOException e){ - e.printStackTrace(); - } - //camera.setPreviewCallback(this); + @Override + public void onEncoderError(String msg){ + gLogger.error("onEncoderEvent: " + msg); } - private void switchToBackground(){ - Intent i = new Intent(); - i.setAction(Intent.ACTION_MAIN); - i.addCategory(Intent.CATEGORY_HOME); - this.startActivity(i); + @Override + public void onPublishing(String msg){ + gLogger.error("onPublishing: " + msg); + mServiceHealthMonitor.record(); } -// -// // class StreamTask AsyncTask -// private class StreamTask extends AsyncTask{ -// private byte[] data; -// -// StreamTask(byte[] data){ -// this.data = data; -// } -// -// @Override -// protected Void doInBackground(Void... params) { -// -// if (this.data != null){ -// Log.e(TAG, "fps: " + mCamera.getParameters().getPreviewFrameRate()); -// ffmpeg.process(this.data); -// } -// return null; -// } -// } - - /*if (null != mStreamTask){ - switch (mStreamTask.getStatus()){ - case RUNNING: - Log.e(TAG, "onPreviewFrame Running"); - return; - case PENDING: - Log.e(TAG,"OnPreviewFrame Pending"); - mStreamTask.cancel(false); - break; - } - } - mStreamTask = new StreamTask(data); - mStreamTask.execute((Void)null); -*/ - -// ong endTime = System.currentTimeMillis(); -// mExecutor.execute(new Runnable() { -// @Override -// public void run() { -// //long encodeTime = System.currentTimeMillis(); -// ffmpeg.process(data); - //Log.e(TAG, "编码第:" + (encodeCount++) + "帧,耗时:" + (System.currentTimeMillis() - encodeTime)); -// } -// }); - //Log.e(TAG, "采集第:" + (++count) + "帧,距上一帧间隔时间:" -// + (endTime - previewTime) + " " + Thread.currentThread().getName()); -// previewTime = endTime;*/ - - - + private void restartApplication() { + final Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } } diff --git a/app/src/main/java/ai/suanzi/rtmpclient/MyLogConfigure.java b/app/src/main/java/ai/suanzi/rtmpclient/MyLogConfigure.java deleted file mode 100644 index 3c63e39..0000000 --- a/app/src/main/java/ai/suanzi/rtmpclient/MyLogConfigure.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.suanzi.rtmpclient; - -import android.content.Context; -import android.os.Environment; -import android.util.Log; - -import de.mindpipe.android.logging.log4j.LogConfigurator; -import org.apache.log4j.Logger; -import org.apache.log4j.Level; -import android.content.Context; - -import java.io.File; - -public class MyLogConfigure { - - private static String logFile = Environment.getExternalStorageDirectory() + File.separator + "log" + "rtmpclient.log"; - public static void setLogName (String file) { - logFile = file; - } - private static MyLogConfigure instance = new MyLogConfigure(); - - private MyLogConfigure(){ - final LogConfigurator logConfigurator = new LogConfigurator(); - //logConfigurator.setFileName(Environment.getExternalStorageDirectory() + File.separator + "myc" + File.separator + "log" + File.separator + "test.log"); - Log.e("log", logFile); - logConfigurator.setFileName(logFile); - logConfigurator.setRootLevel(Level.DEBUG); - logConfigurator.setLevel("org.apache", Level.INFO); - logConfigurator.setMaxFileSize(1024 * 1024 * 10); - logConfigurator.configure(); - } - - public static Logger getLogger(String str) { - if (instance == null) - instance = new MyLogConfigure(); - return Logger.getLogger(str); - } -} \ No newline at end of file diff --git a/app/src/main/java/ai/suanzi/rtmpclient/MyService.java b/app/src/main/java/ai/suanzi/rtmpclient/MyService.java index 0d0136d..d448064 100644 --- a/app/src/main/java/ai/suanzi/rtmpclient/MyService.java +++ b/app/src/main/java/ai/suanzi/rtmpclient/MyService.java @@ -2,43 +2,37 @@ package ai.suanzi.rtmpclient; import android.app.Service; import android.content.Intent; -import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; -import android.util.Log; import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; import android.widget.Toast; import android.support.v4.app.NotificationCompat; import android.graphics.BitmapFactory; import android.app.Notification; -import android.os.Message; -import org.apache.log4j.Level; import org.apache.log4j.Logger; -import android.hardware.Camera.PreviewCallback; -import android.os.IBinder; import android.os.Binder; -import android.content.Context; -import android.graphics.PixelFormat; -import java.io.IOException; -import android.view.Gravity; -public class MyService extends Service implements Camera.PreviewCallback { - private static Logger gLogger = Logger.getLogger("MyService"); - private static String TAG = "MyService"; - private Ffmpeg ffmpeg = Ffmpeg.getInstance(); - private Boolean isRunning = false; +public class MyService extends Service implements Camera.PreviewCallback, Camera.ErrorCallback { + + private static Logger gLogger = Logger.getLogger("MyService"); private Camera mCamera = null; IBinder mBinder = new LocalBinder(); - private String rtmpUrl; + //private String rtmpUrl; private long frameCount = 0; + // Preferred picture Size of the camera; + private int width = 0; + private int height = 0; + + + private MyServiceEventListener mListener; + public void setServiceEventListener(MyServiceEventListener listener){ + mListener = listener; + } public class LocalBinder extends Binder { public MyService getServiceInstance(){ @@ -53,18 +47,35 @@ public class MyService extends Service implements Camera.PreviewCallback { mThread.openCamera(); } } - if (mCamera == null){ - gLogger.error("getCameraInstance, camera is null"); - } return mCamera; } private void openCameraOriginal() { try { gLogger.error("openCameraOriginal"); + Camera.CameraInfo info = new Camera.CameraInfo(); + int numCameras = Camera.getNumberOfCameras(); + int backId = -1; + int frontId = -1; + int camerId = 0; + gLogger.debug("Number of Cameras is " + numCameras); + for(int i = 0; i < numCameras; i++){ + Camera.getCameraInfo(i, info); + if(info.facing == Camera.CameraInfo.CAMERA_FACING_BACK){ + gLogger.debug("CAMERA_FACING_BACK id is " + i); + backId = i; + } else if(info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){ + gLogger.debug("CAMERA_FACING_FRONT id is " + i); + frontId = i; + } + } + if(frontId != -1) camerId = frontId; + else if(backId != -1) camerId = backId; + gLogger.debug("openCameraOriginal - camera id " + camerId); mCamera = Camera.open(1); } catch (Exception e) { - gLogger.error("camera is not available. error: " + e.getMessage()); + gLogger.error("openCameraOriginal - camera is not available. error: " + e.getMessage()); + if(mListener != null) mListener.onCameraError("openCamera - error: " + e.getMessage()); } } @@ -93,7 +104,7 @@ public class MyService extends Service implements Camera.PreviewCallback { try { wait(); } catch (InterruptedException e) { - gLogger.error("wait was interrupted"); + gLogger.error("wait was interrupted, " + e.getMessage()); } } } @@ -122,9 +133,13 @@ public class MyService extends Service implements Camera.PreviewCallback { super.onCreate(); gLogger.error("onCreate ---> "); createNotification(); - Toast.makeText(this, "Video stream pushed to " + this.rtmpUrl, Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Started to Publish!", Toast.LENGTH_LONG).show(); mCamera = getCameraInstance(); - configCamera(mCamera); + if(mCamera != null){ + configCamera(mCamera); + mCamera.setErrorCallback(this); + } + } @Override @@ -134,8 +149,14 @@ public class MyService extends Service implements Camera.PreviewCallback { gLogger.error( "onDestroy --------->"); Toast.makeText(this, "MyService Stopped", Toast.LENGTH_LONG).show(); if(mCamera != null){ - mCamera.stopPreview(); - mCamera.release(); + try { + mCamera.stopPreview(); + mCamera.release(); + mCamera = null; + } catch (Exception e){ + gLogger.error("onDestroy - error " + e.getMessage()); + e.printStackTrace(); + } } } @@ -143,8 +164,6 @@ public class MyService extends Service implements Camera.PreviewCallback { @Override public int onStartCommand(Intent intent, int flags, int startId) { gLogger.error("onStartCommand"); - //nrunnable = new FfmpegRunnable("xxx", this); - //new Thread(runnable).start(); return START_STICKY; } @@ -160,45 +179,67 @@ public class MyService extends Service implements Camera.PreviewCallback { if(frameCount % (15 * 60) == 0) { gLogger.error("onPreviewFrame"); } + if(FfmpegHelper.processFrame(data) != 0){ + gLogger.error("onPreviewFrame, processFrame close"); + FfmpegHelper.close(); + if(mListener != null) mListener.onEncoderError("processFrame"); + } else { + if(frameCount % (15 * 60) == 0){ + if(mListener != null) mListener.onPublishing("processFrame OK"); + } + } frameCount++; - ffmpeg.process(data); } public void startPreview (SurfaceHolder holder){ - gLogger.error("startPreview"); - if (mCamera == null){ - gLogger.error("startPreview - error: camera is null"); - return; - } + gLogger.debug("startPreview"); try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (Exception e){ gLogger.error("startPreview - error: " + e.getMessage()); e.printStackTrace(); + if(mListener != null) mListener.onCameraError("setPreviewDisplay - " + e.getMessage()); + } + } + + public void reopenCamera() { + gLogger.debug("reopenCamera"); + if(mCamera != null){ + mCamera.stopPreview(); + mCamera.release(); + mCamera = null; } + openCameraOriginal(); } public boolean setRtmpUrl (String url){ - this.rtmpUrl = url; - Camera.Parameters param = mCamera.getParameters(); - int width = param.getPictureSize().width; - int height = param.getPictureSize().height; + //this.rtmpUrl = url; + if(mCamera == null){ + gLogger.error("setRtmpUrl, error mCamera is null"); + return false; + } gLogger.error("setRtmpUrl - size: " + width + "x" + height + ". url: " + url); - int ret = ffmpeg.initnew(width, height, url); + int ret = FfmpegHelper.initEncoder(width, height, url); + if(ret != 0){ + gLogger.error("setRtmpUrl, initEncoder error"); + } return ret == 0 ? true : false; } private void configCamera(Camera camera){ - if(mCamera == null){ - gLogger.error("configCamera - camera is null"); + Camera.Parameters paras = null; + try { + paras = camera.getParameters(); + } catch (RuntimeException e){ + gLogger.error("configCamera - " + e.getMessage()); + if(mListener != null) mListener.onCameraError("getParameters - " + e.getMessage()); return; } - Camera.Parameters paras = camera.getParameters(); gLogger.error("Supported Picture Sizes:"); Camera.Size preferredSize = paras.getSupportedPictureSizes().get(0); for (Camera.Size cc : paras.getSupportedPictureSizes()){ - if (cc.width == 640) + if (cc.width == 640) // chose 640 x 480 if exists preferredSize = cc; gLogger.error(cc.width + "x" + cc.height); } @@ -206,6 +247,8 @@ public class MyService extends Service implements Camera.PreviewCallback { for(int[] i : paras.getSupportedPreviewFpsRange()){ gLogger.error("[" + i[0] + "," + i[1] + "]"); } + width = preferredSize.width; + height = preferredSize.height; paras.setPictureSize(preferredSize.width, preferredSize.height); // use 640x480 preferred camera.setParameters(paras); camera.setDisplayOrientation(0); @@ -213,4 +256,28 @@ public class MyService extends Service implements Camera.PreviewCallback { gLogger.error("Picture Format: " + paras.getPictureFormat() + ". Size: " + paras.getPictureSize().width + "x" + paras.getPictureSize().height); camera.setPreviewCallback(this); } + + // Camaer.onError callback + @Override + public void onError(int error, Camera camera){ + gLogger.error("Camera.OnError, " + error); + switch (error) { + case Camera.CAMERA_ERROR_SERVER_DIED: + gLogger.error("CAMERA_ERROR_SERVER_DIED"); + break; + case Camera.CAMERA_ERROR_UNKNOWN: + gLogger.error("CAMERA_ERROR_UNKNOWN"); + break; + } + + if(mListener != null) mListener.onCameraError("OnError, " + error); + } + + + public interface MyServiceEventListener { + void onCameraError(String err); + void onEncoderError(String msg); + //void onIsPreviewing(String msg); // notify mainActivity if preview is running well + void onPublishing(String msg); // notify main activity if encoder is running well; + } } diff --git a/app/src/main/java/ai/suanzi/rtmpclient/NetworkMonitor.java b/app/src/main/java/ai/suanzi/rtmpclient/NetworkMonitor.java new file mode 100644 index 0000000..5513469 --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/NetworkMonitor.java @@ -0,0 +1,63 @@ +package ai.suanzi.rtmpclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.util.Log; +import org.apache.log4j.Logger; +import android.net.ConnectivityManager; +import android.widget.Toast; + +import java.lang.ref.WeakReference; + +public class NetworkMonitor extends BroadcastReceiver { + + private Logger gLogger = Logger.getLogger("NetworkMonitor"); + private NetworkListener mListener; + + public NetworkMonitor(NetworkListener listener) { + mListener = listener; + } + + @Override + public void onReceive (Context context, Intent intent) { + + ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo wifiNetworkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + + //NetworkInfo dataNetworkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + if(wifiNetworkInfo.isConnected()){ + Toast.makeText(context, "Wifi Connected", Toast.LENGTH_SHORT).show(); + gLogger.error("Wifi Connected"); + mListener.onWifiConnected(); + } else { + Toast.makeText(context, "Wifi Disconnected", Toast.LENGTH_SHORT).show(); + gLogger.error("Wifi Disconnected"); + mListener.onWifiDisconnected(); + } + } + + public static boolean isNetworkAvailable(Context context){ + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + NetworkInfo info = connectivity.getActiveNetworkInfo(); + if (info != null && info.isConnected()) { + if (info.getState() == NetworkInfo.State.CONNECTED) { + return true; + } + } + } + return false; + } + + public interface NetworkListener { + void onWifiConnected(); + void onWifiDisconnected(); + void onWifiEnabled(); + void onWifiDisabled(); + } +} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/ServiceHealthMonitor.java b/app/src/main/java/ai/suanzi/rtmpclient/ServiceHealthMonitor.java new file mode 100644 index 0000000..e77a28c --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/ServiceHealthMonitor.java @@ -0,0 +1,50 @@ +package ai.suanzi.rtmpclient; + +import org.apache.log4j.Logger; +public class ServiceHealthMonitor extends Thread { + private long count = 0; + private long lastCnt = 0; + private int interval = 60; // seconds + + private Callback mCallback; + + public ServiceHealthMonitor(Callback cb){ + mCallback = cb; + } + + private Logger gLogger = Logger.getLogger("ServiceHealthMonitor"); + @Override + public void run(){ + while (true){ + try { + sleep(interval * 1000); + }catch (InterruptedException e){ + gLogger.error("sleep - error: " + e.getMessage()); + } + if(!isHealth()) + if(mCallback != null) mCallback.onNotHealthy(); + } + } + + public synchronized void record(){ + gLogger.error("record " + count); + count++; + } + + public synchronized void setInterval (int time) { + interval = time; + } + + private synchronized boolean isHealth (){ + if(count == lastCnt) { + gLogger.error("isHealth - count = " + count + ". lastCnt " + lastCnt); + return false; + } + lastCnt = count; + return true; + } + + public interface Callback { + void onNotHealthy(); + } +} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/UVCCamera.java b/app/src/main/java/ai/suanzi/rtmpclient/UVCCamera.java deleted file mode 100644 index 4b22cf4..0000000 --- a/app/src/main/java/ai/suanzi/rtmpclient/UVCCamera.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.suanzi.rtmpclient; - -import android.util.Log; - -public class UVCCamera { - - static { - System.loadLibrary("usb100"); - System.loadLibrary("uvc"); - System.loadLibrary("ffmpeg-jni"); - //System.loadLibrary("UVCCamera"); - } - - public UVCCamera(){ - Log.e("UVC", " uvc camera"); - //init(); - } - - public native int open(int venderId, int prodId, int fd, int busNum, int devNum, String usbFsName); - public native void init(); - -} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/UsbCamera.java b/app/src/main/java/ai/suanzi/rtmpclient/UsbCamera.java deleted file mode 100644 index 0ca1023..0000000 --- a/app/src/main/java/ai/suanzi/rtmpclient/UsbCamera.java +++ /dev/null @@ -1,4 +0,0 @@ -package ai.suanzi.rtmpclient; - -public class UsbCamera { -} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/UsbMonitor.java b/app/src/main/java/ai/suanzi/rtmpclient/UsbMonitor.java new file mode 100644 index 0000000..c862ff0 --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/UsbMonitor.java @@ -0,0 +1,115 @@ +package ai.suanzi.rtmpclient; + +import android.hardware.usb.UsbManager; +import android.content.Context; +import android.hardware.usb.UsbDevice; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import org.apache.log4j.Logger; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; + +public class UsbMonitor { + + private UsbListener mListener; + private Context mContext; + private UsbManager mUsbManager; + private List mUsbCameraList = new ArrayList<>(); + + private static Logger gLogger = Logger.getLogger("UsbMonitor"); + + public UsbMonitor(UsbListener listener, Context context){ + mListener = listener; + mContext = context; + mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE); + + HashMap deviceList = mUsbManager.getDeviceList(); + gLogger.error("device list size : " + deviceList.size()); + Iterator deviceIterator = deviceList.values().iterator(); + //PendingIntent mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0); + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); + mContext.registerReceiver(mUsbReceiver, filter); + filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED); + mContext.registerReceiver(mUsbReceiver, filter); + filter = new IntentFilter(ACTION_USB_PERMISSION); + mContext.registerReceiver(mUsbReceiver, filter); + + while (deviceIterator.hasNext()) { + UsbDevice device = deviceIterator.next(); + gLogger.error(device.toString()); + if(isUvcCamera(device)) mUsbCameraList.add(device); + } + //mUsbManager.requestPermission(device, mPermissionIntent); + } + + public void unregisterReceiver (){ + mContext.unregisterReceiver(mUsbReceiver); + + } + + private boolean isUvcCamera(UsbDevice device){ + return (device.getDeviceProtocol() == 1 && device.getProductName().contains("Camera")); + } + + public boolean hasUsbCamera(){ + gLogger.error("hasUsbCamera - size: " + mUsbCameraList.size()); + return (mUsbCameraList.size() > 0 ? true : false); + } + + public int getUsbCameraCount(){ + return mUsbCameraList.size(); + } + + private static final String ACTION_USB_PERMISSION = "ai.suanzi.rtmpclient.UsbMonitor.USB_PERMISSION"; + + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + + public void onReceive(Context context, Intent intent) { + gLogger.error("onReceive"); + String action = intent.getAction(); + if (ACTION_USB_PERMISSION.equals(action)) { + synchronized (this) { + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true)) { + if(device != null){ + gLogger.error( "extra permission granted"); + //call method to set up device communication + } + } + else { + gLogger.error("permission denied for device. " + device); + } + } + } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)){ + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null){ + gLogger.error("Device plug out"); + gLogger.error(device.toString()); + if(isUvcCamera(device)) { + mUsbCameraList.remove(device); + mListener.onCameraDisconnected(); + } + } + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { + UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + gLogger.error("Device plug in"); + gLogger.error(device.toString()); + if(isUvcCamera(device)) { + mUsbCameraList.add(device); + mListener.onCameraConnected(); + } + } + } + }; + + public interface UsbListener { + void onCameraConnected(); + void onCameraDisconnected(); + } +} diff --git a/app/src/main/java/ai/suanzi/rtmpclient/UserInfo.java b/app/src/main/java/ai/suanzi/rtmpclient/UserInfo.java index 9d95894..8009127 100644 --- a/app/src/main/java/ai/suanzi/rtmpclient/UserInfo.java +++ b/app/src/main/java/ai/suanzi/rtmpclient/UserInfo.java @@ -1,5 +1,8 @@ package ai.suanzi.rtmpclient; +import android.content.Context; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.widget.Toast; import java.io.BufferedReader; @@ -23,6 +26,8 @@ public class UserInfo { private static String configPath; private static Logger gLogger = Logger.getLogger("UserInfo"); + private static String DEFAULT_SERVER = "rtmp://gpussh.suanzi.ai:1935/myapp"; + private static String DEFAULT_USER = "yunzhi"; private static UserInfo instance = null; private UserInfo () {} @@ -132,9 +137,11 @@ public class UserInfo { } } - public String toUrl () { + public String toUrl (String mac) { //rtmp://gpussh.suanzi.ai:1935/myapp/suanzi_ac83f34ead90_cameraid - return server + "/" + user + "_" + macAddr + "_" + cameraId; + //return server + "/" + user + "_" + macAddr + "_" + cameraId; + return (server.equals("") ? DEFAULT_SERVER : server) + "/" + (user.equals("") ? DEFAULT_USER : user) + "_" + mac; + } public String getValue(String key){ diff --git a/app/src/main/java/ai/suanzi/rtmpclient/WifiReceiver.java b/app/src/main/java/ai/suanzi/rtmpclient/WifiReceiver.java new file mode 100644 index 0000000..5a85509 --- /dev/null +++ b/app/src/main/java/ai/suanzi/rtmpclient/WifiReceiver.java @@ -0,0 +1,47 @@ +package ai.suanzi.rtmpclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; + +import org.apache.log4j.Logger; +import android.net.wifi.WifiInfo; +import android.util.Log; + +public class WifiReceiver extends BroadcastReceiver { + + private Logger gLogger = Logger.getLogger("WifiReceiver"); + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) { + gLogger.error("Wifi rssi changed"); + } + + if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)){ + NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + if(info.getState().equals(NetworkInfo.State.DISCONNECTED)){ + Log.e("WIFI", "wowowo"); + gLogger.error("Wifi Disconnected"); + } else if (info.getState().equals(NetworkInfo.State.CONNECTED)){ + gLogger.error("Wifi Connected"); + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + //获取当前wifi名称 + gLogger.error("连接到网络 " + wifiInfo.getSSID()); + //TtsManager ttsManager = new TtsManager(); + //ttsManager.checkTtsJet(context.getApplicationContext()); + + } + } + if(intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED); + if(wifiState == WifiManager.WIFI_STATE_DISABLED){ + gLogger.error("Wifi Disabled"); + } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { + gLogger.error("Wifi Enabled"); + } + } + } +} diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk index d6f060b..5a8b542 100644 --- a/app/src/main/jni/Android.mk +++ b/app/src/main/jni/Android.mk @@ -2,8 +2,8 @@ JNI_PATH := $(call my-dir) #include $(JNI_PATH)/libusb-1.0.22/android/jni/Android.mk #include $(JNI_PATH)/libuvc-0.0.6/android/jni/Android.mk -include $(JNI_PATH)/libusb/android/jni/Android.mk -include $(JNI_PATH)/libuvc/android/jni/Android.mk +#include $(JNI_PATH)/libusb/android/jni/Android.mk +#include $(JNI_PATH)/libuvc/android/jni/Android.mk @@ -91,25 +91,36 @@ include $(PREBUILT_SHARED_LIBRARY) # ffmpegjni +#include $(CLEAR_VARS) +#LOCAL_MODULE := ffmpeg-jni +#LOCAL_SRC_FILES := ai_suanzi_rtmpclient_Ffmpeg.cpp +#LOCAL_C_INCLUDES := $(UVC_ROOT)/include +#ifeq ($(TARGET_ARCH),x86) +#LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 +#else +#LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc +#endif +#LOCAL_CFLAGS := -D__ANDROID_API__=21 +#LOCAL_CFLAGS += -I$(LOCAL_PATH)/../ffmpeg-3.0.11/include +#LOCAL_CFLAGS += -I$(FFMPEG_DIR)/include +#LOCAL_CFLAGS += -Ijni/ffmpeg-3.0.11/include +#LOCAL_LDLIBS :=-llog -landroid +#include $(BUILD_SHARED_LIBRARY) + +# FfmpegHelperJNI include $(CLEAR_VARS) -LOCAL_MODULE := ffmpeg-jni -LOCAL_SRC_FILES := ai_suanzi_rtmpclient_Ffmpeg.cpp \ - ai_suanzi_rtmpclient_UVCCamera.cpp \ - UVCCamera.cpp +LOCAL_MODULE := FfmpegHelperJNI +LOCAL_SRC_FILES := ai_suanzi_rtmpclient_FfmpegHelper.cpp \ + FfmpegHelper.cpp LOCAL_C_INCLUDES := $(UVC_ROOT)/include ifeq ($(TARGET_ARCH),x86) -LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 uvc +LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc x264 else -LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc uvc +LOCAL_SHARED_LIBRARIES := avdevice avcodec avformat avfilter swresample swscale avutil postproc endif -LOCAL_CFLAGS := -D__ANDROID_API__=21 -#LOCAL_CFLAGS += -I$(LOCAL_PATH)/../ffmpeg-3.0.11/include -#LOCAL_CFLAGS += -I$(FFMPEG_DIR)/include -#LOCAL_CFLAGS += -Ijni/ffmpeg-3.0.11/include - - +#LOCAL_CFLAGS := -D__ANDROID_API__=21 LOCAL_LDLIBS :=-llog -landroid include $(BUILD_SHARED_LIBRARY) diff --git a/app/src/main/jni/Application.mk b/app/src/main/jni/Application.mk index 5965130..08f2103 100644 --- a/app/src/main/jni/Application.mk +++ b/app/src/main/jni/Application.mk @@ -1 +1,2 @@ -APP_ABI := armeabi-v7a x86 \ No newline at end of file +APP_ABI := armeabi-v7a +APP_STL := c++_shared \ No newline at end of file diff --git a/app/src/main/jni/FfmpegHelper.cpp b/app/src/main/jni/FfmpegHelper.cpp new file mode 100644 index 0000000..71d7064 --- /dev/null +++ b/app/src/main/jni/FfmpegHelper.cpp @@ -0,0 +1,276 @@ +#include "FfmpegHelper.h" +#include "log.h" +#include + +#define FLOGE(...) av_log(NULL, AV_LOG_ERROR, __VA_ARGS__) +#define FLOGD(...) av_log(NULL, AV_LOG_INFO, __VA_ARGS__) + +FfmpegHelper* FfmpegHelper::singleton = NULL; +bool FfmpegHelper::isInit = false; +bool FfmpegHelper::isEncoderReady = false; + + +FfmpegHelper::FfmpegHelper(JavaVM *vm, jclass cls) +: jvm(vm) +, ai_suanzi_rtmpclient_FfmpegHelper(cls) +{ +} + + +jint FfmpegHelper::nativeOnLoad(JavaVM * vm, void* reserved) +{ + JNIEnv* env; + if(vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK){ + return -1; + } + + jclass local_ref = 0; + if (env) local_ref = env->FindClass ("ai/suanzi/rtmpclient/FfmpegHelper"); + jclass global_ref = reinterpret_cast (env->NewGlobalRef (local_ref)); + singleton = new FfmpegHelper(vm, global_ref); + return JNI_VERSION_1_6; +} + +void FfmpegHelper::av_log_cb (void *ptr, int level, const char* fmt, va_list vl) +{ + static int print_prefix = 1; + char line[1024]; + av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix); + + if (level <= AV_LOG_WARNING){ + if (singleton) singleton->javaPrint(line, 1); + } else if(level <= AV_LOG_INFO){ + if (singleton) singleton->javaPrint(line, 0); + // LOGE("%s", line); + } else { + + } +} + +void FfmpegHelper::javaPrint(const char* str, int level) +{ + JNIEnv* env = 0; + if(this->jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK){ + return; + } + jmethodID mid = env->GetStaticMethodID(ai_suanzi_rtmpclient_FfmpegHelper, "javaPrint", "(Ljava/lang/String;I)V"); + jstring jstr = env->NewStringUTF(str); + env->CallStaticVoidMethod(ai_suanzi_rtmpclient_FfmpegHelper, mid, jstr, level); + env->DeleteLocalRef(jstr); +} + +void FfmpegHelper::init() +{ + av_log_set_callback(av_log_cb); + av_log_set_level(AV_LOG_DEBUG); + FLOGD("########## Ffmpeg Init ##########"); + unsigned int v = avutil_version(); + FLOGD("libavutil - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avcodec_version(); + FLOGD("libavcodec - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avformat_version(); + FLOGD("libavformat - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + v = avdevice_version(); + FLOGD("libavdevice - %d.%d.%d", AV_VERSION_MAJOR(v), AV_VERSION_MINOR(v), AV_VERSION_MICRO(v)); + + av_register_all(); + //avdevice_register_all(); + int ret = 0; + if((ret = avformat_network_init()) != 0){ + FLOGE("avformat_network_init, error:%s(%d)", av_err2str(ret), ret); + } + isInit = true; + isEncoderReady = false; +} + +int FfmpegHelper::initEncoder(int width, int height, const char* outpath) +{ + if(!isInit) init(); + FLOGE("-----------> FfmpegHelper::initEncoder"); + FLOGD("initEncoder - width=%d, height=%d, url=%s", width, height, outpath); + + pWidth = width; + pHeight = height; + int ret = 0; + + avformat_alloc_output_context2(&formatCtx, NULL, "flv", outpath); + + // initial encoder + if((codec = avcodec_find_encoder(AV_CODEC_ID_H264)) == NULL){ + FLOGE("Can not find encoder!\n"); + return -1; + } + codecCtx = avcodec_alloc_context3(codec); + codecCtx->pix_fmt = AV_PIX_FMT_YUV420P; + codecCtx->width = width; + codecCtx->height = height; + codecCtx->time_base.num = 1; + codecCtx->time_base.den = 30; + codecCtx->bit_rate = 800000; + codecCtx->gop_size = 300; + if (formatCtx->oformat->flags & AVFMT_GLOBALHEADER) /* Some formats want stream headers to be separate. */ + codecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + //H264 codec param + //pCodecCtx->me_range = 16; + //pCodecCtx->max_qdiff = 4; + //pCodecCtx->qcompress = 0.6; + codecCtx->qmin = 10; + codecCtx->qmax = 51; + //Optional Param + codecCtx->max_b_frames = 3; + // Set H264 preset and tune + AVDictionary *param = 0; + av_dict_set(¶m, "preset", "ultrafast", 0); + av_dict_set(¶m, "tune", "zerolatency", 0); + if ((ret = avcodec_open2(codecCtx, codec, ¶m)) < 0){ + LOGE("Failed to open encoder!, error:%s(%d)\n", av_err2str(ret), ret); + return -1; + } + + //Add a new stream to output,should be called by the user before avformat_write_header() for muxing + if ((vStream = avformat_new_stream(formatCtx, codec)) == NULL){ + FLOGE("avformat_new_stream - error"); + return -1; + } + vStream->time_base.num = 1; + vStream->time_base.den = 30; + vStream->codec = codecCtx; + + //Open output URL,set before avformat_write_header() for muxing + if (( ret = avio_open(&formatCtx->pb, outpath, AVIO_FLAG_READ_WRITE)) < 0){ + LOGE("Failed to open output file! error :%s(%d)\n", av_err2str(ret), ret); + return -1; + } + + if((ret = avformat_write_header(formatCtx, NULL)) != 0){ //Write File Header + LOGE("avformat_write_header error :%s(%d)\n", av_err2str(ret), ret); + return -1; + } + startTime = av_gettime(); + frameCnt = 0; + isEncoderReady = true; + return 0; +} + + +int FfmpegHelper::processFrame(uint8_t* data) +{ + if(!isEncoderReady){ + FLOGE("processFrame - isEncoderReady is false."); + return -1; + } + int ret = 0; + int y_length = pWidth * pHeight; + pFrameYUV = av_frame_alloc(); + uint8_t *outBuf = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1)); + if(outBuf == NULL) { + FLOGE("av_malloc, error"); + av_frame_free(&pFrameYUV); + return -1; + } + if((ret = av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuf, AV_PIX_FMT_YUV420P, codecCtx->width, codecCtx->height, 1)) < 0){ + FLOGE("av_image_fill_arrays - error: %s(%d).", av_err2str(ret), ret); + av_frame_free(&pFrameYUV); + return -1; + } + + // NV21 to YUV420P + memcpy(pFrameYUV->data[0], data, y_length); + for (int i = 0; i < y_length / 4; i++){ + *(pFrameYUV->data[2] + i) = *(data + y_length + i * 2); + *(pFrameYUV->data[1] + i) = *(data + y_length + i * 2 + 1); + } + + pFrameYUV->format = AV_PIX_FMT_YUV420P; + pFrameYUV->width = pWidth; + pFrameYUV->height = pHeight; + + + encPkt.data = NULL; + encPkt.size = 0; + av_init_packet(&encPkt); + int got_frame = 0; + + if((ret = avcodec_encode_video2(codecCtx, &encPkt, pFrameYUV, &got_frame)) < 0){ + FLOGE("avcodec_encode_video2 - error: %s(%d).", av_err2str(ret), ret); + av_frame_free(&pFrameYUV); + return -1; + } + av_frame_free(&pFrameYUV); + + if(got_frame == 1){ + if (frameCnt % (15 * 60) == 0){ + FLOGD("Succeed to encode frame: %5d\tsize:%5d\n", frameCnt, encPkt.size); + } + frameCnt++; + encPkt.stream_index = vStream->index; + + // PTS + AVRational time_base = formatCtx->streams[0]->time_base;//{ 1, 1000 }; + AVRational r_framerate = {60, 2 };//{ 50, 2 }; + AVRational time_base_q = { 1, AV_TIME_BASE }; + //Duration between 2 frames (us) + int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate)); //内部时间戳 + //Parameters + //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base)); + encPkt.pts = av_rescale_q(frameCnt*calc_duration, time_base_q, time_base); + encPkt.dts = encPkt.pts; + encPkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base)); + encPkt.pos = -1; + + //Delay + int64_t pts_time = av_rescale_q(encPkt.dts, time_base, time_base_q); + int64_t now_time = av_gettime() - startTime; + if (pts_time > now_time) + av_usleep(pts_time - now_time); + + if((ret = av_interleaved_write_frame(formatCtx, &encPkt)) < 0){ + FLOGE("av_interleaved_write_frame - error: %s(%d)", av_err2str(ret), ret); + return -1; + } + av_packet_unref(&encPkt); + } + av_free(outBuf); + return 0; +} + +int FfmpegHelper::close() +{ + if(vStream){ + avcodec_close(vStream->codec); + vStream = NULL; + } + + if (formatCtx){ + avio_close(formatCtx->pb); + avformat_free_context(formatCtx); + formatCtx = NULL; + } + FLOGE("<----------- FfmpegHelper::close "); + isEncoderReady = false; + return 0; +} + + +jint FfmpegHelper::nativeInitEncoder(JNIEnv *env, jclass cls, jint width, jint height, jstring url) +{ + const char* output = env->GetStringUTFChars(url, 0); + int ret = 0; + if (singleton) ret = singleton->initEncoder(width, height, output); + env->ReleaseStringUTFChars(url, output); + return ret; +} + +jint FfmpegHelper::nativeProcessFrame(JNIEnv *env, jclass cls, jbyteArray data) +{ + jbyte* buf = (jbyte*)env->GetByteArrayElements(data, 0); + int ret = 0; + if(singleton) ret = singleton->processFrame((uint8_t *)buf); + return ret; +} + +jint FfmpegHelper::nativeClose() +{ + if(singleton) return singleton->close(); + return 0; +} diff --git a/app/src/main/jni/FfmpegHelper.h b/app/src/main/jni/FfmpegHelper.h new file mode 100644 index 0000000..c79512c --- /dev/null +++ b/app/src/main/jni/FfmpegHelper.h @@ -0,0 +1,53 @@ +#ifndef __FFMPEG_HELPER_H__ +#define __FFMPEG_HELPER_H__ + +#include +#include + +extern "C" { + #include "libavformat/avformat.h" + #include "libavcodec/avcodec.h" + #include "libswscale/swscale.h" + #include "libavutil/imgutils.h" + #include "libavutil/time.h" + #include "libavdevice/avdevice.h" +} + +class FfmpegHelper { +public: + static jint nativeOnLoad(JavaVM * jvm, void* reserved); + static jint nativeInitEncoder(JNIEnv *env, jclass cls, jint width, jint height, jstring url); + static jint nativeProcessFrame(JNIEnv *env, jclass cls, jbyteArray data); + static jint nativeClose(); + +private: + static void av_log_cb (void *ptr, int level, const char* fmt, va_list vl); + void javaPrint(const char* str, int level); + static void init(); + int processFrame(uint8_t *data); + int initEncoder(int width, int height, const char* url); + int close(); + +private: + FfmpegHelper(JavaVM* vm, jclass cls); + +private: + static FfmpegHelper* singleton; + JavaVM *jvm; + jclass ai_suanzi_rtmpclient_FfmpegHelper; + static bool isInit; + + AVFormatContext* formatCtx; + AVCodecContext* codecCtx; + AVCodec* codec; + AVStream* vStream; + AVPacket encPkt; + AVFrame *pFrameYUV; + int pWidth; + int pHeight; + unsigned int frameCnt; + int64_t startTime; + static bool isEncoderReady; +}; + +#endif /* __FFMPEG_HELPER_H__ */ diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp b/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp index 6bfea3a..32f4b67 100644 --- a/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp +++ b/app/src/main/jni/ai_suanzi_rtmpclient_Ffmpeg.cpp @@ -76,7 +76,6 @@ void custom_log(void *ptr, int level, const char* fmt, va_list vl){ if (level <= AV_LOG_WARNING){ LOGE("%s", line); - } else { // LOGE("%s", line); } @@ -142,7 +141,6 @@ JNIEXPORT void JNICALL Java_ai_suanzi_rtmpclient_Ffmpeg_setRtmpUrl (JNIEnv *env, LOGE("set rtmpurl"); //const char* out_path = "rtmp://gpussh.suanzi.ai:1935/myapp/suanzi_ac83f34ead90_cameraid"; //out_path = env->GetStringUTFChars(url, 0); - } diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp new file mode 100644 index 0000000..b87bb34 --- /dev/null +++ b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.cpp @@ -0,0 +1,42 @@ +// +// Created by Peng Li on 18/5/2018. +// +#include "ai_suanzi_rtmpclient_FfmpegHelper.h" +#include "FfmpegHelper.h" + + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved) +{ + return FfmpegHelper::nativeOnLoad(vm, reserved); +} + + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: initialDecoder + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_initEncoder (JNIEnv *env, jclass cls, jint width, jint height, jstring url) +{ + return FfmpegHelper::nativeInitEncoder(env, cls, width, height, url); +} + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: processFrame + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_processFrame(JNIEnv *env, jclass cls, jbyteArray data) +{ + return FfmpegHelper::nativeProcessFrame(env, cls, data); +} + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: close + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_close (JNIEnv *env, jclass cls) +{ + return FfmpegHelper::nativeClose(); +} diff --git a/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h new file mode 100644 index 0000000..303ca28 --- /dev/null +++ b/app/src/main/jni/ai_suanzi_rtmpclient_FfmpegHelper.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class ai_suanzi_rtmpclient_FfmpegHelper */ + +#ifndef _Included_ai_suanzi_rtmpclient_FfmpegHelper +#define _Included_ai_suanzi_rtmpclient_FfmpegHelper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: initEncoder + * Signature: (IILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_initEncoder + (JNIEnv *, jclass, jint, jint, jstring); + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: processFrame + * Signature: ([B)I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_processFrame + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: ai_suanzi_rtmpclient_FfmpegHelper + * Method: close + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_ai_suanzi_rtmpclient_FfmpegHelper_close + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif