diff --git a/EasyPusher_Android/.gitignore b/EasyPusher_Android/.gitignore new file mode 100644 index 0000000..4c9e2c6 --- /dev/null +++ b/EasyPusher_Android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/EasyPusher_Android/app/.gitignore b/EasyPusher_Android/app/.gitignore new file mode 100644 index 0000000..3543521 --- /dev/null +++ b/EasyPusher_Android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/EasyPusher_Android/app/build.gradle b/EasyPusher_Android/app/build.gradle new file mode 100644 index 0000000..9b437c5 --- /dev/null +++ b/EasyPusher_Android/app/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "org.easydarwin.easypusher" + minSdkVersion 16 + targetSdkVersion 23 + versionCode 1 + versionName "1.0.16.0309" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/EasyPusher_Android/app/proguard-rules.pro b/EasyPusher_Android/app/proguard-rules.pro new file mode 100644 index 0000000..a88dc4b --- /dev/null +++ b/EasyPusher_Android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\AndroidStudio\StudioSDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/EasyPusher_Android/app/src/androidTest/java/org/easydarwin/easypusher/ApplicationTest.java b/EasyPusher_Android/app/src/androidTest/java/org/easydarwin/easypusher/ApplicationTest.java new file mode 100644 index 0000000..3832554 --- /dev/null +++ b/EasyPusher_Android/app/src/androidTest/java/org/easydarwin/easypusher/ApplicationTest.java @@ -0,0 +1,19 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ +package org.easydarwin.easypusher; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/EasyPusher_Android/app/src/main/AndroidManifest.xml b/EasyPusher_Android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2004b1f --- /dev/null +++ b/EasyPusher_Android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/config/Config.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/config/Config.java new file mode 100644 index 0000000..0a6bd2c --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/config/Config.java @@ -0,0 +1,24 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ + +package org.easydarwin.config; + +/** + * 类Config的实现描述://TODO 类实现描述 + * + * @author HELONG 2016/3/7 19:02 + */ +public class Config { + + public static final String SERVER_IP="serverIp"; + public static final String SERVER_PORT="serverPort"; + public static final String STREAM_ID="streamId"; + public static final String DEFAULT_SERVER_IP="www.easydss.com"; + public static final String DEFAULT_SERVER_PORT="554"; + public static final String DEFAULT_STREAM_ID="easydarwin_stream"; + +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/MainActivity.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/MainActivity.java new file mode 100644 index 0000000..6777daa --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/MainActivity.java @@ -0,0 +1,351 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ +package org.easydarwin.easypusher; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.ImageFormat; +import android.hardware.Camera; +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import org.easydarwin.util.Util; +import org.easydarwin.config.Config; +import org.easydarwin.hw.EncoderDebugger; +import org.easydarwin.hw.NV21Convertor; +import org.easydarwin.push.EasyPusher; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.List; + +@SuppressWarnings("deprecation") +public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, View.OnClickListener { + + int width = 640, height = 480; + int framerate, bitrate; + int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; + MediaCodec mMediaCodec; + SurfaceView surfaceView; + SurfaceHolder surfaceHolder; + Camera mCamera; + NV21Convertor mConvertor; + Button btnSwitch; + Button btnSetting; + // boolean started = false; + boolean pushStream = false;//是否要推送数据 + EasyPusher mEasyPusher; + TextView txtStreamAddress; + String serverIP = "", serverPort = "", streamID = ""; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + btnSwitch = (Button) findViewById(R.id.btn_switch); + btnSwitch.setOnClickListener(this); + btnSetting = (Button) findViewById(R.id.btn_setting); + btnSetting.setOnClickListener(this); + txtStreamAddress = (TextView) findViewById(R.id.txt_stream_address); + initMediaCodec(); + surfaceView = (SurfaceView) findViewById(R.id.sv_surfaceview); + surfaceView.getHolder().addCallback(this); + surfaceView.getHolder().setFixedSize(getResources().getDisplayMetrics().widthPixels, + getResources().getDisplayMetrics().heightPixels); + mEasyPusher = new EasyPusher(); + } + + @Override + protected void onResume() { + super.onResume(); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + String ip = sharedPreferences.getString(Config.SERVER_IP, Config.DEFAULT_SERVER_IP); + String port = sharedPreferences.getString(Config.SERVER_PORT, Config.DEFAULT_SERVER_PORT); + String id = sharedPreferences.getString(Config.STREAM_ID, ""); + if (TextUtils.isEmpty(id)) { + id = String.valueOf(System.nanoTime()); + SharedPreferences sharedPreferences1 = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + sharedPreferences.edit().putString(Config.STREAM_ID, id).commit(); + } + if (serverIP.equals(ip) && serverPort.equals(port) && id.equals(streamID)) { + return; + } + serverIP = ip; + serverPort = port; + streamID = id; + txtStreamAddress.setText(String.format("rtsp://%s:%s/%s.sdp", ip, port, id)); + mEasyPusher.initPush(ip, port, String.format("%s.sdp", id)); + } + + private void initMediaCodec() { + int dgree = getDgree(); + framerate = 15; + bitrate = 2 * width * height * framerate / 20; + EncoderDebugger debugger = EncoderDebugger.debug(getApplicationContext(), width, height); + mConvertor = debugger.getNV21Convertor(); + try { + mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName()); + MediaFormat mediaFormat; + if (dgree == 0) { + mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width); + } else { + mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height); + } + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); + mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate); + mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, + debugger.getEncoderColorFormat()); + mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mMediaCodec.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static int[] determineMaximumSupportedFramerate(Camera.Parameters parameters) { + int[] maxFps = new int[]{0, 0}; + List supportedFpsRanges = parameters.getSupportedPreviewFpsRange(); + for (Iterator it = supportedFpsRanges.iterator(); it.hasNext(); ) { + int[] interval = it.next(); + if (interval[1] > maxFps[1] || (interval[0] > maxFps[0] && interval[1] == maxFps[1])) { + maxFps = interval; + } + } + return maxFps; + } + + private boolean ctreateCamera(SurfaceHolder surfaceHolder) { + try { + mCamera = Camera.open(mCameraId); + Camera.Parameters parameters = mCamera.getParameters(); + int[] max = determineMaximumSupportedFramerate(parameters); + Camera.CameraInfo camInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(mCameraId, camInfo); + int cameraRotationOffset = camInfo.orientation; + int rotate = (360 + cameraRotationOffset - getDgree()) % 360; + parameters.setRotation(rotate); + parameters.setPreviewFormat(ImageFormat.NV21); + List sizes = parameters.getSupportedPreviewSizes(); + parameters.setPreviewSize(width, height); + parameters.setPreviewFpsRange(max[0], max[1]); + mCamera.setParameters(parameters); + mCamera.autoFocus(null); + int displayRotation; + displayRotation = (cameraRotationOffset - getDgree() + 360) % 360; + mCamera.setDisplayOrientation(displayRotation); + mCamera.setPreviewDisplay(surfaceHolder); + return true; + } catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stack = sw.toString(); + Toast.makeText(this, stack, Toast.LENGTH_LONG).show(); + destroyCamera(); + e.printStackTrace(); + return false; + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + surfaceHolder = holder; + ctreateCamera(surfaceHolder); + startPreview(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + stopPreview(); + stopStream(); + destroyCamera(); + } + + Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { + byte[] mPpsSps = new byte[0]; + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + if (data == null || !pushStream) { + mCamera.addCallbackBuffer(data); + return; + } + ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); + ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers(); + byte[] dst = new byte[data.length]; + Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); + if (getDgree() == 0) { + dst=Util.rotateNV21Degree90(data, previewSize.width, previewSize.height); + } else { + dst = data; + } + try { + int bufferIndex = mMediaCodec.dequeueInputBuffer(5000000); + if (bufferIndex >= 0) { + inputBuffers[bufferIndex].clear(); + mConvertor.convert(dst, inputBuffers[bufferIndex]); + mMediaCodec.queueInputBuffer(bufferIndex, 0, + inputBuffers[bufferIndex].position(), + System.nanoTime() / 1000, 0); + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); + while (outputBufferIndex >= 0) { + ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; + byte[] outData = new byte[bufferInfo.size]; + outputBuffer.get(outData); + //记录pps和sps + if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 103) { + mPpsSps = outData; + } else if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 101) { + //在关键帧前面加上pps和sps数据 + byte[] iframeData = new byte[mPpsSps.length + outData.length]; + System.arraycopy(mPpsSps, 0, iframeData, 0, mPpsSps.length); + System.arraycopy(outData, 0, iframeData, mPpsSps.length, outData.length); + outData = iframeData; + } + mEasyPusher.push(outData, 0); + mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); + outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); + } + } else { + Log.e("easypusher", "No buffer available !"); + } + } catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stack = sw.toString(); + Log.e("save_log", stack); + e.printStackTrace(); + } finally { + mCamera.addCallbackBuffer(dst); + } + } + + }; + + /** + * 开启预览 + */ + public synchronized void startPreview() { + if (mCamera != null) { + mCamera.startPreview(); + int previewFormat = mCamera.getParameters().getPreviewFormat(); + Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); + int size = previewSize.width * previewSize.height + * ImageFormat.getBitsPerPixel(previewFormat) + / 8; + mCamera.addCallbackBuffer(new byte[size]); + mCamera.setPreviewCallbackWithBuffer(previewCallback); + } + } + + /** + * 停止预览 + */ + public synchronized void stopPreview() { + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.setPreviewCallbackWithBuffer(null); + } + } + + public void startStream() { + pushStream = true; + btnSwitch.setText("停止"); + } + + public void stopStream() { + pushStream = false; + btnSwitch.setText("开始"); + } + + + /** + * 销毁Camera + */ + protected synchronized void destroyCamera() { + if (mCamera != null) { + mCamera.stopPreview(); + try { + mCamera.release(); + } catch (Exception e) { + + } + mCamera = null; + } + } + + private int getDgree() { + int rotation = getWindowManager().getDefaultDisplay().getRotation(); + int degrees = 0; + switch (rotation) { + case Surface.ROTATION_0: + degrees = 0; + break; // Natural orientation + case Surface.ROTATION_90: + degrees = 90; + break; // Landscape left + case Surface.ROTATION_180: + degrees = 180; + break;// Upside down + case Surface.ROTATION_270: + degrees = 270; + break;// Landscape right + } + return degrees; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_switch: + if (!pushStream) { + startStream(); + } else { + stopStream(); + } + break; + case R.id.btn_setting: + startActivity(new Intent(this, SettingActivity.class)); + break; + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + destroyCamera(); + mMediaCodec.stop(); + mMediaCodec.release(); + mMediaCodec = null; + mEasyPusher.stop(); + } +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SettingActivity.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SettingActivity.java new file mode 100644 index 0000000..0335146 --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SettingActivity.java @@ -0,0 +1,83 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ + +package org.easydarwin.easypusher; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import org.easydarwin.config.Config; +import org.w3c.dom.Text; + +public class SettingActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_setting); + final EditText txtIp = (EditText) findViewById(R.id.edt_server_address); + final EditText txtPort = (EditText) findViewById(R.id.edt_server_port); + final EditText txtId = (EditText) findViewById(R.id.edt_stream_id); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + String ip = sharedPreferences.getString(Config.SERVER_IP, Config.DEFAULT_SERVER_IP); + String port = sharedPreferences.getString(Config.SERVER_PORT, Config.DEFAULT_SERVER_PORT); + String id = sharedPreferences.getString(Config.STREAM_ID, Config.DEFAULT_STREAM_ID); + + txtIp.setText(ip); + txtPort.setText(port); + txtId.setText(id); + + Button btnSave = (Button) findViewById(R.id.btn_save); + btnSave.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String ipValue = txtIp.getText().toString(); + String portValue = txtPort.getText().toString(); + String idValue = txtId.getText().toString(); + + if (TextUtils.isEmpty(ipValue)) { + ipValue = Config.DEFAULT_SERVER_IP; + } + + if (TextUtils.isEmpty(portValue)) { + portValue = Config.DEFAULT_SERVER_PORT; + } + + if (TextUtils.isEmpty(idValue)) { + idValue = Config.DEFAULT_STREAM_ID; + } + + SharedPreferences sharedPreferences = PreferenceManager + .getDefaultSharedPreferences(SettingActivity.this); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(Config.SERVER_IP, ipValue); + editor.putString(Config.SERVER_PORT, portValue); + editor.putString(Config.STREAM_ID, idValue); + editor.commit(); + onBackPressed(); + } + }); + } + + private void showToast(String message) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SplashActivity.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SplashActivity.java new file mode 100644 index 0000000..7563c18 --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/easypusher/SplashActivity.java @@ -0,0 +1,50 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ +package org.easydarwin.easypusher; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.widget.TextView; + +import org.easydarwin.config.Config; + +public class SplashActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.splash_activity); + + new Handler().postDelayed(new Runnable() { + + @Override + public void run() { + startActivity(new Intent(SplashActivity.this, MainActivity.class)); + SplashActivity.this.finish(); + } + }, 2000); + + String versionname; + try { + versionname = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + versionname = getResources().getString(R.string.version); + } + + TextView txtVersion = (TextView) findViewById(R.id.txt_version); + txtVersion.setText(String.format("v%s", versionname)); + + } + +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/CodecManager.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/CodecManager.java new file mode 100644 index 0000000..b15248c --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/CodecManager.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2011-2014 GUIGUI Simon, fyhertz@gmail.com + * + * This file is part of Spydroid (http://code.google.com/p/spydroid-ipcamera/) + * + * Spydroid is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this source code; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.easydarwin.hw; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import android.annotation.SuppressLint; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.util.Log; + +@SuppressLint("InlinedApi") +public class CodecManager { + + public final static String TAG = "CodecManager"; + + public static final int[] SUPPORTED_COLOR_FORMATS = { + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar + }; + + private static Codec[] sEncoders = null; + private static Codec[] sDecoders = null; + + static class Codec { + public Codec(String name, Integer[] formats) { + this.name = name; + this.formats = formats; + } + public String name; + public Integer[] formats; + } + + /** + * Lists all encoders that claim to support a color format that we know how to use. + * @return A list of those encoders + */ + @SuppressLint("NewApi") + public synchronized static Codec[] findEncodersForMimeType(String mimeType) { + if (sEncoders != null) return sEncoders; + + ArrayList encoders = new ArrayList(); + + // We loop through the encoders, apparently this can take up to a sec (testes on a GS3) + for(int j = MediaCodecList.getCodecCount() - 1; j >= 0; j--){ + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(j); + if (!codecInfo.isEncoder()) continue; + + String[] types = codecInfo.getSupportedTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i].equalsIgnoreCase(mimeType)) { + try { + MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType); + Set formats = new HashSet(); + + // And through the color formats supported + for (int k = 0; k < capabilities.colorFormats.length; k++) { + int format = capabilities.colorFormats[k]; + + for (int l=0;l decoders = new ArrayList(); + + // We loop through the decoders, apparently this can take up to a sec (testes on a GS3) + for(int j = MediaCodecList.getCodecCount() - 1; j >= 0; j--){ + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(j); + if (codecInfo.isEncoder()) continue; + + String[] types = codecInfo.getSupportedTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i].equalsIgnoreCase(mimeType)) { + try { + MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType); + Set formats = new HashSet(); + + // And through the color formats supported + for (int k = 0; k < capabilities.colorFormats.length; k++) { + int format = capabilities.colorFormats[k]; + + for (int l=0;l + * Feeding the encoder with a surface is not tested here. Some bugs you may have + * encountered:
+ *
    + *
  • U and V panes reversed
  • + *
  • Some padding is needed after the Y pane
  • + *
  • stride!=width or slice-height!=height
  • + *
+ */ +@SuppressLint("NewApi") +public class EncoderDebugger { + + public final static String TAG = "EncoderDebugger"; + + /** + * Prefix that will be used for all shared preferences saved by + * libstreaming. + */ + private static final String PREF_PREFIX = "libstreaming-"; + + /** + * If this is set to false the test will be run only once and the result + * will be saved in the shared preferences. + */ + private static final boolean DEBUG = false; + + /** + * Set this to true to see more logs. + */ + private static final boolean VERBOSE = false; + + /** + * Will be incremented every time this test is modified. + */ + private static final int VERSION = 3; + + /** + * Bitrate that will be used with the encoder. + */ + private final static int BITRATE = 1000000; + + /** + * Framerate that will be used to test the encoder. + */ + private final static int FRAMERATE = 20; + + private final static String MIME_TYPE = "video/avc"; + + private final static int NB_DECODED = 34; + private final static int NB_ENCODED = 50; + + private int mEncoderColorFormat; + private String mEncoderName, mErrorLog; + private MediaCodec mEncoder; + private int mWidth, mHeight, mSize; + private byte[] mSPS, mPPS; + + private byte[] mData, mInitialImage; + private NV21Convertor mNV21; + private SharedPreferences mPreferences; + private byte[][] mVideo, mDecodedVideo; + private String mB64PPS, mB64SPS; + + public synchronized static void asyncDebug(final Context context, + final int width, final int height) { + new Thread(new Runnable() { + @Override + public void run() { + try { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + debug(prefs, width, height); + } catch (Exception e) { + } + } + }).start(); + } + + public synchronized static EncoderDebugger debug(Context context, int width, int height) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + return debug(prefs, width, height); + } + + public synchronized static EncoderDebugger debug(SharedPreferences prefs, int width, int height) { + EncoderDebugger debugger = new EncoderDebugger(prefs, width, height); + debugger.debug(); + return debugger; + } + + public String getB64PPS() { + return mB64PPS; + } + + public String getB64SPS() { + return mB64SPS; + } + + public String getEncoderName() { + return mEncoderName; + } + + public int getEncoderColorFormat() { + return mEncoderColorFormat; + } + + /** + * This {@link NV21Convertor} will do the necessary work to feed properly + * the encoder. + */ + public NV21Convertor getNV21Convertor() { + return mNV21; + } + + /** + * A log of all the errors that occured during the test. + */ + public String getErrorLog() { + return mErrorLog; + } + + private EncoderDebugger(SharedPreferences prefs, int width, int height) { + mPreferences = prefs; + mWidth = width; + mHeight = height; + mSize = width * height; + reset(); + } + + private void reset() { + mNV21 = new NV21Convertor(); + mVideo = new byte[NB_ENCODED][]; + mDecodedVideo = new byte[NB_DECODED][]; + mErrorLog = ""; + mPPS = null; + mSPS = null; + } + + private void debug() { + + // If testing the phone again is not needed, + // we just restore the result from the shared preferences + if (!checkTestNeeded()) { + String resolution = mWidth + "x" + mHeight + "-"; + + boolean success = mPreferences.getBoolean(PREF_PREFIX + resolution + + "success", false); + if (!success) { + throw new RuntimeException( + "Phone not supported with this resolution (" + mWidth + + "x" + mHeight + ")"); + } + + mNV21.setSize(mWidth, mHeight); + mNV21.setSliceHeigth(mPreferences.getInt(PREF_PREFIX + resolution + + "sliceHeight", 0)); + mNV21.setStride(mPreferences.getInt(PREF_PREFIX + resolution + + "stride", 0)); + mNV21.setYPadding(mPreferences.getInt(PREF_PREFIX + resolution + + "padding", 0)); + mNV21.setPlanar(mPreferences.getBoolean(PREF_PREFIX + resolution + + "planar", false)); + mNV21.setColorPanesReversed(mPreferences.getBoolean(PREF_PREFIX + + resolution + "reversed", false)); + mEncoderName = mPreferences.getString(PREF_PREFIX + resolution + + "encoderName", ""); + mEncoderColorFormat = mPreferences.getInt(PREF_PREFIX + resolution + + "colorFormat", 0); + mB64PPS = mPreferences.getString(PREF_PREFIX + resolution + "pps", + ""); + mB64SPS = mPreferences.getString(PREF_PREFIX + resolution + "sps", + ""); + + return; + } + + if (VERBOSE) + Log.d(TAG, ">>>> Testing the phone for resolution " + mWidth + "x" + + mHeight); + + // Builds a list of available encoders and decoders we may be able to + // use + // because they support some nice color formats + Codec[] encoders = CodecManager.findEncodersForMimeType(MIME_TYPE); + Codec[] decoders = CodecManager.findDecodersForMimeType(MIME_TYPE); + + int count = 0, n = 1; + for (int i = 0; i < encoders.length; i++) { + count += encoders[i].formats.length; + } + + // Tries available encoders + for (int i = 0; i < encoders.length; i++) { + for (int j = 0; j < encoders[i].formats.length; j++) { + reset(); + + mEncoderName = encoders[i].name; + mEncoderColorFormat = encoders[i].formats[j]; + + if (VERBOSE) + Log.v(TAG, ">> Test " + (n++) + "/" + count + ": " + + mEncoderName + " with color format " + + mEncoderColorFormat + " at " + mWidth + "x" + + mHeight); + + // Converts from NV21 to YUV420 with the specified parameters + mNV21.setSize(mWidth, mHeight); + mNV21.setSliceHeigth(mHeight); + mNV21.setStride(mWidth); + mNV21.setYPadding(0); + mNV21.setEncoderColorFormat(mEncoderColorFormat); + + // /!\ NV21Convertor can directly modify the input + createTestImage(); + mData = mNV21.convert(mInitialImage); + + try { + + // Starts the encoder + configureEncoder(); + searchSPSandPPS(); + + saveTestResult(true); + Log.v(TAG, "The encoder " + mEncoderName + + " is usable with resolution " + mWidth + "x" + + mHeight); + return; + + } catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stack = sw.toString(); + String str = "Encoder " + mEncoderName + + " cannot be used with color format " + + mEncoderColorFormat; + if (VERBOSE) + Log.e(TAG, str, e); + mErrorLog += str + "\n" + stack; + e.printStackTrace(); + } finally { + releaseEncoder(); + } + + } + } + + saveTestResult(false); + Log.e(TAG, "No usable encoder were found on the phone for resolution " + + mWidth + "x" + mHeight); + throw new RuntimeException( + "No usable encoder were found on the phone for resolution " + + mWidth + "x" + mHeight); + + } + + + private boolean checkTestNeeded() { + String resolution = mWidth + "x" + mHeight + "-"; + + // Forces the test + if (DEBUG || mPreferences == null) + return true; + + // If the sdk has changed on the phone, or the version of the test + // it has to be run again + if (mPreferences.contains(PREF_PREFIX + resolution + "lastSdk")) { + int lastSdk = mPreferences.getInt(PREF_PREFIX + resolution + + "lastSdk", 0); + int lastVersion = mPreferences.getInt(PREF_PREFIX + resolution + + "lastVersion", 0); + if (Build.VERSION.SDK_INT > lastSdk || VERSION > lastVersion) { + return true; + } + } else { + return true; + } + return false; + } + + /** + * Saves the result of the test in the shared preferences, we will run it + * again only if the SDK has changed on the phone, or if this test has been + * modified. + */ + private void saveTestResult(boolean success) { + String resolution = mWidth + "x" + mHeight + "-"; + Editor editor = mPreferences.edit(); + + editor.putBoolean(PREF_PREFIX + resolution + "success", success); + + if (success) { + editor.putInt(PREF_PREFIX + resolution + "lastSdk", + Build.VERSION.SDK_INT); + editor.putInt(PREF_PREFIX + resolution + "lastVersion", VERSION); + editor.putInt(PREF_PREFIX + resolution + "sliceHeight", + mNV21.getSliceHeigth()); + editor.putInt(PREF_PREFIX + resolution + "stride", + mNV21.getStride()); + editor.putInt(PREF_PREFIX + resolution + "padding", + mNV21.getYPadding()); + editor.putBoolean(PREF_PREFIX + resolution + "planar", + mNV21.getPlanar()); + editor.putBoolean(PREF_PREFIX + resolution + "reversed", + mNV21.getUVPanesReversed()); + editor.putString(PREF_PREFIX + resolution + "encoderName", + mEncoderName); + editor.putInt(PREF_PREFIX + resolution + "colorFormat", + mEncoderColorFormat); + editor.putString(PREF_PREFIX + resolution + "encoderName", + mEncoderName); + editor.putString(PREF_PREFIX + resolution + "pps", mB64PPS); + editor.putString(PREF_PREFIX + resolution + "sps", mB64SPS); + } + + editor.commit(); + } + + /** + * Creates the test image that will be used to feed the encoder. + */ + private void createTestImage() { + mInitialImage = new byte[3 * mSize / 2]; + for (int i = 0; i < mSize; i++) { + mInitialImage[i] = (byte) (40 + i % 199); + } + for (int i = mSize; i < 3 * mSize / 2; i += 2) { + mInitialImage[i] = (byte) (40 + i % 200); + mInitialImage[i + 1] = (byte) (40 + (i + 99) % 200); + } + + } + + /** + * Converts the image obtained from the decoder to NV21. + */ + + /** + * Instantiates and starts the encoder. + * + * @throws IOException + */ + private void configureEncoder() throws IOException { + mEncoder = MediaCodec.createByCodecName(mEncoderName); + MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, + mWidth, mHeight); + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BITRATE); + mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAMERATE); + mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, + mEncoderColorFormat); + mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + mEncoder.configure(mediaFormat, null, null, + MediaCodec.CONFIGURE_FLAG_ENCODE); + mEncoder.start(); + } + + private void releaseEncoder() { + if (mEncoder != null) { + try { + mEncoder.stop(); + } catch (Exception ignore) { + } + try { + mEncoder.release(); + } catch (Exception ignore) { + } + } + + } + + /** + * Tries to obtain the SPS and the PPS for the encoder. + */ + private long searchSPSandPPS() { + long elapsed = 0, now = timestamp(); + ByteBuffer[] inputBuffers = mEncoder.getInputBuffers(); + ByteBuffer[] outputBuffers = mEncoder.getOutputBuffers(); + BufferInfo info = new BufferInfo(); + byte[] csd = new byte[128]; + int len = 0, p = 4, q = 4; + + while (elapsed < 3000000 && (mSPS == null || mPPS == null)) { + + // Some encoders won't give us the SPS and PPS unless they receive + // something to encode first... + int bufferIndex = mEncoder.dequeueInputBuffer(1000000 / FRAMERATE); + if (bufferIndex >= 0) { + check(inputBuffers[bufferIndex].capacity() >= mData.length, + "The input buffer is not big enough."); + inputBuffers[bufferIndex].clear(); + inputBuffers[bufferIndex].put(mData, 0, mData.length); + mEncoder.queueInputBuffer(bufferIndex, 0, mData.length, + timestamp(), 0); + } else { + if (VERBOSE) + Log.e(TAG, "No buffer available !"); + } + + // We are looking for the SPS and the PPS here. As always, Android + // is very inconsistent, I have observed that some + // encoders will give those parameters through the MediaFormat + // object (that is the normal behaviour). + // But some other will not, in that case we try to find a NAL unit + // of type 7 or 8 in the byte stream outputed by the encoder... + + int index = mEncoder.dequeueOutputBuffer(info, 1000000 / FRAMERATE); + + if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + + // The PPS and PPS shoud be there + MediaFormat format = mEncoder.getOutputFormat(); + ByteBuffer spsb = format.getByteBuffer("csd-0"); + ByteBuffer ppsb = format.getByteBuffer("csd-1"); + mSPS = new byte[spsb.capacity() - 4]; + spsb.position(4); + spsb.get(mSPS, 0, mSPS.length); + mPPS = new byte[ppsb.capacity() - 4]; + ppsb.position(4); + ppsb.get(mPPS, 0, mPPS.length); + break; + + } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + outputBuffers = mEncoder.getOutputBuffers(); + } else if (index >= 0) { + + len = info.size; + if (len < 128) { + outputBuffers[index].get(csd, 0, len); + if (len > 0 && csd[0] == 0 && csd[1] == 0 && csd[2] == 0 + && csd[3] == 1) { + // Parses the SPS and PPS, they could be in two + // different packets and in a different order + // depending on the phone so we don't make any + // assumption about that + while (p < len) { + while (!(csd[p + 0] == 0 && csd[p + 1] == 0 + && csd[p + 2] == 0 && csd[p + 3] == 1) + && p + 3 < len) + p++; + if (p + 3 >= len) + p = len; + if ((csd[q] & 0x1F) == 7) { + mSPS = new byte[p - q]; + System.arraycopy(csd, q, mSPS, 0, p - q); + } else { + mPPS = new byte[p - q]; + System.arraycopy(csd, q, mPPS, 0, p - q); + } + p += 4; + q = p; + } + } + } + mEncoder.releaseOutputBuffer(index, false); + } + + elapsed = timestamp() - now; + } + + check(mPPS != null & mSPS != null, "Could not determine the SPS & PPS."); + mB64PPS = Base64.encodeToString(mPPS, 0, mPPS.length, Base64.NO_WRAP); + mB64SPS = Base64.encodeToString(mSPS, 0, mSPS.length, Base64.NO_WRAP); + + return elapsed; + } + + static int getXPS(byte[] data, int offset, int length, byte[] dataOut, + int[] outLen, int type) { + int i; + int pos0; + int pos1; + pos0 = -1; + for (i = offset; i < length - 4; i++) { + if ((0 == data[i]) && (0 == data[i + 1]) && (1 == data[i + 2]) + && (type == (0x0F & data[i + 3]))) { + pos0 = i; + break; + } + } + if (-1 == pos0) { + return -1; + } + pos1 = -1; + for (i = pos0 + 4; i < length - 4; i++) { + if ((0 == data[i]) && (0 == data[i + 1]) && (0 == data[i + 2])) { + pos1 = i; + break; + } + } + if (-1 == pos1) { + return -2; + } + if (pos1 - pos0 + 1 > outLen[0]) { + return -3; // 输入缓冲区太小 + } + dataOut[0] = 0; + System.arraycopy(data, pos0, dataOut, 1, pos1 - pos0); + // memcpy(pXPS+1, pES+pos0, pos1-pos0); + // *pMaxXPSLen = pos1-pos0+1; + outLen[0] = pos1 - pos0 + 1; + return 0; + } + + + private void check(boolean cond, String message) { + if (!cond) { + if (VERBOSE) + Log.e(TAG, message); + throw new IllegalStateException(message); + } + } + + private long timestamp() { + return System.nanoTime() / 1000; + } + + @Override + public String toString() { + return "EncoderDebugger [mEncoderColorFormat=" + mEncoderColorFormat + + ", mEncoderName=" + mEncoderName + ", mErrorLog=" + mErrorLog + ", mEncoder=" + + mEncoder + ", mWidth=" + mWidth + + ", mHeight=" + mHeight + ", mSize=" + mSize + ", mSPS=" + + Arrays.toString(mSPS) + ", mPPS=" + Arrays.toString(mPPS) + + ", mData=" + Arrays.toString(mData) + ", mInitialImage=" + + Arrays.toString(mInitialImage) + ", mNV21=" + mNV21 + ", mPreferences=" + + mPreferences + ", mVideo=" + Arrays.toString(mVideo) + + ", mDecodedVideo=" + Arrays.toString(mDecodedVideo) + + ", mB64PPS=" + mB64PPS + ", mB64SPS=" + mB64SPS + + "]"; + } + + +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/NV21Convertor.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/NV21Convertor.java new file mode 100644 index 0000000..0290867 --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/hw/NV21Convertor.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2011-2014 GUIGUI Simon, fyhertz@gmail.com + * + * This file is part of Spydroid (http://code.google.com/p/spydroid-ipcamera/) + * + * Spydroid is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this source code; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.easydarwin.hw; + +import java.nio.ByteBuffer; + +import android.media.MediaCodecInfo; +import android.util.Log; + +/** + * Converts from NV21 to YUV420 semi planar or planar. + */ +public class NV21Convertor { + + private int mSliceHeight, mHeight; + + private int mStride, mWidth; + + private int mSize; + + private boolean mPlanar, mPanesReversed = false; + + private int mYPadding; + + private byte[] mBuffer; + + ByteBuffer mCopy; + + public void setSize(int width, int height) { + mHeight = height; + mWidth = width; + mSliceHeight = height; + mStride = width; + mSize = mWidth * mHeight; + } + + public void setStride(int width) { + mStride = width; + } + + public void setSliceHeigth(int height) { + mSliceHeight = height; + } + + public void setPlanar(boolean planar) { + mPlanar = planar; + } + + public void setYPadding(int padding) { + mYPadding = padding; + } + + public int getBufferSize() { + return 3 * mSize / 2; + } + + public void setEncoderColorFormat(int colorFormat) { + switch (colorFormat) { + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: + case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: + setPlanar(false); + break; + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: + case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: + setPlanar(true); + break; + } + } + + public void setColorPanesReversed(boolean b) { + mPanesReversed = b; + } + + public int getStride() { + return mStride; + } + + public int getSliceHeigth() { + return mSliceHeight; + } + + public int getYPadding() { + return mYPadding; + } + + public boolean getPlanar() { + return mPlanar; + } + + public boolean getUVPanesReversed() { + return mPanesReversed; + } + + public void convert(byte[] data, ByteBuffer buffer) { + byte[] result = convert(data); + int min = buffer.capacity() < data.length ? buffer.capacity() : data.length; + buffer.put(result, 0, min); + } + + public byte[] convert(byte[] data) { + + // A buffer large enough for every case + if (mBuffer == null || mBuffer.length != 3 * mSliceHeight * mStride / 2 + mYPadding) { + mBuffer = new byte[3 * mSliceHeight * mStride / 2 + mYPadding]; + } + + if (!mPlanar) { + if (mSliceHeight == mHeight && mStride == mWidth) { + // Swaps U and V + if (!mPanesReversed) { + for (int i = mSize; i < mSize + mSize / 2; i += 2) { + mBuffer[0] = data[i + 1]; + data[i + 1] = data[i]; + data[i] = mBuffer[0]; + } + } + if (mYPadding > 0) { + System.arraycopy(data, 0, mBuffer, 0, mSize); + System.arraycopy(data, mSize, mBuffer, mSize + mYPadding, mSize / 2); + return mBuffer; + } + return data; + } + } + else { + if (mSliceHeight == mHeight && mStride == mWidth) { + // De-interleave U and V + if (!mPanesReversed) { + for (int i = 0; i < mSize / 4; i += 1) { + mBuffer[i] = data[mSize + 2 * i + 1]; + mBuffer[mSize / 4 + i] = data[mSize + 2 * i]; + } + } + else { + for (int i = 0; i < mSize / 4; i += 1) { + mBuffer[i] = data[mSize + 2 * i]; + mBuffer[mSize / 4 + i] = data[mSize + 2 * i + 1]; + } + } + if (mYPadding == 0) { + System.arraycopy(mBuffer, 0, data, mSize, mSize / 2); + } + else { + System.arraycopy(data, 0, mBuffer, 0, mSize); + System.arraycopy(mBuffer, 0, mBuffer, mSize + mYPadding, mSize / 2); + return mBuffer; + } + return data; + } + } + + return data; + } + +} diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/push/EasyPusher.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/push/EasyPusher.java new file mode 100644 index 0000000..3ed0ce3 --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/push/EasyPusher.java @@ -0,0 +1,58 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ +package org.easydarwin.push; + +public class EasyPusher { + + static { + System.loadLibrary("easypusher"); + } + + /** + * 初始化 + * + * @param serverIP 服务器IP + * @param serverPort 服务端口 + * @param streamName 流名称 + */ + public native void init(String serverIP, String serverPort, String streamName); + + /** + * 推送编码后的H264数据 + * + * @param data H264数据 + * @param timestamp 时间戳,毫秒 + */ + public native void push(byte[] data, int timestamp); + + + /** + * 停止推送 + */ + private native void stopPush(); + + public void stop() { + new Thread(new Runnable() { + @Override + public void run() { + stopPush(); + } + }).start(); + } + + public void initPush(final String serverIP, final String serverPort, final String streamName){ + new Thread(new Runnable() { + @Override + public void run() { + stopPush(); + init(serverIP,serverPort,streamName); + } + }).start(); + } + +} + diff --git a/EasyPusher_Android/app/src/main/java/org/easydarwin/util/Util.java b/EasyPusher_Android/app/src/main/java/org/easydarwin/util/Util.java new file mode 100644 index 0000000..3d637e3 --- /dev/null +++ b/EasyPusher_Android/app/src/main/java/org/easydarwin/util/Util.java @@ -0,0 +1,83 @@ +/* + Copyright (c) 2013-2016 EasyDarwin.ORG. All rights reserved. + Github: https://github.com/EasyDarwin + WEChat: EasyDarwin + Website: http://www.easydarwin.org +*/ + +package org.easydarwin.util; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * 类Util的实现描述://TODO 类实现描述 + * + * @author HELONG 2016/3/8 17:42 + */ +public class Util { + + /** + * 将YUV420SP数据顺时针旋转90度 + * + * @param data 要旋转的数据 + * @param imageWidth 要旋转的图片宽度 + * @param imageHeight 要旋转的图片高度 + * @return 旋转后的数据 + */ + public static byte[] rotateNV21Degree90(byte[] data, int imageWidth, int imageHeight) { + byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2]; + // Rotate the Y luma + int i = 0; + for (int x = 0; x < imageWidth; x++) { + for (int y = imageHeight - 1; y >= 0; y--) { + yuv[i] = data[y * imageWidth + x]; + i++; + } + } + // Rotate the U and V color components + i = imageWidth * imageHeight * 3 / 2 - 1; + for (int x = imageWidth - 1; x > 0; x = x - 2) { + for (int y = 0; y < imageHeight / 2; y++) { + yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x]; + i--; + yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)]; + i--; + } + } + return yuv; + } + + /** + * 保存数据到本地 + * + * @param buffer 要保存的数据 + * @param offset 要保存数据的起始位置 + * @param length 要保存数据长度 + * @param path 保存路径 + * @param append 是否追加 + */ + public static void save(byte[] buffer, int offset, int length, String path, boolean append) { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(path, append); + fos.write(buffer, offset, length); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + fos.flush(); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + +} diff --git a/EasyPusher_Android/app/src/main/jniLibs/arm64-v8a/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/arm64-v8a/libeasypusher.so new file mode 100644 index 0000000..e8a138d Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/arm64-v8a/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/armeabi-v7a/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/armeabi-v7a/libeasypusher.so new file mode 100644 index 0000000..ecd07b2 Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/armeabi-v7a/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/armeabi/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/armeabi/libeasypusher.so new file mode 100644 index 0000000..034690c Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/armeabi/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/mips/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/mips/libeasypusher.so new file mode 100644 index 0000000..84f080d Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/mips/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/mips64/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/mips64/libeasypusher.so new file mode 100644 index 0000000..eb1f815 Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/mips64/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/x86/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/x86/libeasypusher.so new file mode 100644 index 0000000..1061c82 Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/x86/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/jniLibs/x86_64/libeasypusher.so b/EasyPusher_Android/app/src/main/jniLibs/x86_64/libeasypusher.so new file mode 100644 index 0000000..ca56840 Binary files /dev/null and b/EasyPusher_Android/app/src/main/jniLibs/x86_64/libeasypusher.so differ diff --git a/EasyPusher_Android/app/src/main/res/drawable-nodpi/easy_logo.png b/EasyPusher_Android/app/src/main/res/drawable-nodpi/easy_logo.png new file mode 100644 index 0000000..d2a78dd Binary files /dev/null and b/EasyPusher_Android/app/src/main/res/drawable-nodpi/easy_logo.png differ diff --git a/EasyPusher_Android/app/src/main/res/drawable/button_selector.xml b/EasyPusher_Android/app/src/main/res/drawable/button_selector.xml new file mode 100644 index 0000000..780d55f --- /dev/null +++ b/EasyPusher_Android/app/src/main/res/drawable/button_selector.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPusher_Android/app/src/main/res/layout/activity_main.xml b/EasyPusher_Android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f6a1570 --- /dev/null +++ b/EasyPusher_Android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,41 @@ + + + + + +