Skip to content
Snippets Groups Projects
Commit c42c84a8 authored by Leo Ma's avatar Leo Ma
Browse files

SDK


Signed-off-by: default avatarLeo Ma <begeekmyfriend@gmail.com>
parent b2ec6c8e
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -4,16 +4,11 @@ import android.app.Activity;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
Loading
Loading
@@ -21,14 +16,11 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Random;
import net.ossrs.yasea.rtmp.RtmpPublisher;
 
public class MainActivity extends Activity implements SurfaceHolder.Callback, Camera.PreviewCallback {
import java.util.Random;
public class MainActivity extends Activity {
private static final String TAG = "Yasea";
 
Button btnPublish = null;
Loading
Loading
@@ -36,130 +28,12 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
Button btnRecord = null;
Button btnSwitchEncoder = null;
 
private AudioRecord mic = null;
private boolean aloop = false;
private Thread aworker = null;
private SurfaceView mCameraView = null;
private Camera mCamera = null;
private int videoFrameCount;
private long lastTimeMillis;
private int mPreviewRotation = 90;
private int mCamId = Camera.getNumberOfCameras() - 1; // default camera
private byte[] mYuvFrameBuffer = new byte[SrsEncoder.VPREV_WIDTH * SrsEncoder.VPREV_HEIGHT * 3 / 2];
private String mNotifyMsg;
private SharedPreferences sp;
private String rtmpUrl = "rtmp://ossrs.net/" + getRandomAlphaString(3) + '/' + getRandomAlphaDigitString(5);
private String recPath = Environment.getExternalStorageDirectory().getPath() + "/test.mp4";
 
private SrsFlvMuxer flvMuxer = new SrsFlvMuxer(new RtmpPublisher.EventHandler() {
@Override
public void onRtmpConnecting(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpConnected(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpVideoStreaming(String msg) {
}
@Override
public void onRtmpAudioStreaming(String msg) {
}
@Override
public void onRtmpStopped(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpDisconnected(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpOutputFps(final double fps) {
Log.i(TAG, String.format("Output Fps: %f", fps));
}
});
private SrsMp4Muxer mp4Muxer = new SrsMp4Muxer(new SrsMp4Muxer.EventHandler() {
@Override
public void onRecordPause(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordResume(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordStarted(String msg) {
mNotifyMsg = "Recording file: " + msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordFinished(String msg) {
mNotifyMsg = "MP4 file saved: " + msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
});
private SrsEncoder mEncoder = new SrsEncoder(flvMuxer, mp4Muxer);
private SrsPublisher mPublisher = new SrsPublisher();
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -172,20 +46,19 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
 
// restore data.
sp = getSharedPreferences("SrsPublisher", MODE_PRIVATE);
sp = getSharedPreferences("Yasea", MODE_PRIVATE);
rtmpUrl = sp.getString("rtmpUrl", rtmpUrl);
 
// initialize url.
final EditText efu = (EditText) findViewById(R.id.url);
efu.setText(rtmpUrl);
 
// for camera, @see https://developer.android.com/reference/android/hardware/Camera.html
btnPublish = (Button) findViewById(R.id.publish);
btnSwitchCamera = (Button) findViewById(R.id.swCam);
btnRecord = (Button) findViewById(R.id.record);
btnSwitchEncoder = (Button) findViewById(R.id.swEnc);
mCameraView = (SurfaceView) findViewById(R.id.preview);
mCameraView.getHolder().addCallback(this);
mPublisher.setSurfaceView((SurfaceView) findViewById(R.id.preview));
 
btnPublish.setOnClickListener(new View.OnClickListener() {
@Override
Loading
Loading
@@ -197,16 +70,10 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
editor.putString("rtmpUrl", rtmpUrl);
editor.commit();
 
try {
flvMuxer.start(rtmpUrl);
} catch (IOException e) {
Log.e(TAG, "start FLV muxer failed.");
e.printStackTrace();
return;
}
flvMuxer.setVideoResolution(mEncoder.VOUT_WIDTH, mEncoder.VOUT_HEIGHT);
startEncoder();
mPublisher.setPreviewResolution(1280, 720);
mPublisher.setOutputResolution(384, 640);
mPublisher.setVideoHDMode();
mPublisher.startPublish(rtmpUrl);
 
if (btnSwitchEncoder.getText().toString().contentEquals("soft enc")) {
Toast.makeText(getApplicationContext(), "Use hard encoder", Toast.LENGTH_SHORT).show();
Loading
Loading
@@ -216,9 +83,8 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
btnPublish.setText("stop");
btnSwitchEncoder.setEnabled(false);
} else if (btnPublish.getText().toString().contentEquals("stop")) {
stopEncoder();
flvMuxer.stop();
mp4Muxer.stop();
mPublisher.stopPublish();
btnPublish.setText("publish");
btnRecord.setText("record");
btnSwitchEncoder.setEnabled(true);
Loading
Loading
@@ -229,11 +95,8 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
btnSwitchCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mCamera != null && mEncoder != null) {
mCamId = (mCamId + 1) % Camera.getNumberOfCameras();
stopCamera();
mEncoder.swithCameraFace();
startCamera();
if (mPublisher.getNumberOfCameras() > 0) {
mPublisher.switchCameraFace((mPublisher.getCamraId() + 1) % mPublisher.getNumberOfCameras());
}
}
});
Loading
Loading
@@ -242,18 +105,14 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
@Override
public void onClick(View v) {
if (btnRecord.getText().toString().contentEquals("record")) {
try {
mp4Muxer.record(new File(recPath));
} catch (IOException e) {
Log.e(TAG, "start MP4 muxer failed.");
e.printStackTrace();
}
mPublisher.startRecord(recPath);
btnRecord.setText("pause");
} else if (btnRecord.getText().toString().contentEquals("pause")) {
mp4Muxer.pause();
mPublisher.pauseRecord();
btnRecord.setText("resume");
} else if (btnRecord.getText().toString().contentEquals("resume")) {
mp4Muxer.resume();
mPublisher.resumeRecord();
btnRecord.setText("pause");
}
}
Loading
Loading
@@ -262,18 +121,121 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
btnSwitchEncoder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mEncoder != null) {
if (btnSwitchEncoder.getText().toString().contentEquals("soft enc")) {
mEncoder.swithToSoftEncoder();
btnSwitchEncoder.setText("hard enc");
} else if (btnSwitchEncoder.getText().toString().contentEquals("hard enc")) {
mEncoder.swithToHardEncoder();
btnSwitchEncoder.setText("soft enc");
}
if (btnSwitchEncoder.getText().toString().contentEquals("soft enc")) {
mPublisher.swithToSoftEncoder();
btnSwitchEncoder.setText("hard enc");
} else if (btnSwitchEncoder.getText().toString().contentEquals("hard enc")) {
mPublisher.swithToHardEncoder();
btnSwitchEncoder.setText("soft enc");
}
}
});
 
mPublisher.setPublishEventHandler(new RtmpPublisher.EventHandler() {
@Override
public void onRtmpConnecting(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpConnected(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpVideoStreaming(String msg) {
}
@Override
public void onRtmpAudioStreaming(String msg) {
}
@Override
public void onRtmpStopped(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpDisconnected(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRtmpOutputFps(final double fps) {
Log.i(TAG, String.format("Output Fps: %f", fps));
}
});
mPublisher.setRecordEventHandler(new SrsMp4Muxer.EventHandler() {
@Override
public void onRecordPause(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordResume(String msg) {
mNotifyMsg = msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordStarted(String msg) {
mNotifyMsg = "Recording file: " + msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onRecordFinished(String msg) {
mNotifyMsg = "MP4 file saved: " + msg;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_SHORT).show();
}
});
}
});
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Loading
Loading
@@ -282,9 +244,8 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
@Override
public void run() {
Toast.makeText(getApplicationContext(), mNotifyMsg, Toast.LENGTH_LONG).show();
stopEncoder();
flvMuxer.stop();
mp4Muxer.stop();
mPublisher.stopPublish();
mPublisher.stopRecord();
btnPublish.setText("publish");
btnRecord.setText("record");
btnSwitchEncoder.setEnabled(true);
Loading
Loading
@@ -316,175 +277,42 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
return super.onOptionsItemSelected(item);
}
 
private void startCamera() {
if (mCamera != null) {
Log.d(TAG, "start camera, already started. return");
return;
}
if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {
Log.e(TAG, "####### start camera failed, inviald params, camera No.="+ mCamId);
return;
}
mCamera = Camera.open(mCamId);
Camera.Parameters params = mCamera.getParameters();
/* preview size */
Size size = mCamera.new Size(SrsEncoder.VPREV_WIDTH, SrsEncoder.VPREV_HEIGHT);
if (!params.getSupportedPreviewSizes().contains(size)) {
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
new IllegalArgumentException(String.format("Unsupported preview size %dx%d", size.width, size.height)));
}
/* picture size */
if (!params.getSupportedPictureSizes().contains(size)) {
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
new IllegalArgumentException(String.format("Unsupported picture size %dx%d", size.width, size.height)));
}
/***** set parameters *****/
//params.set("orientation", "portrait");
//params.set("orientation", "landscape");
//params.setRotation(90);
params.setPictureSize(SrsEncoder.VPREV_WIDTH, SrsEncoder.VPREV_HEIGHT);
params.setPreviewSize(SrsEncoder.VPREV_WIDTH, SrsEncoder.VPREV_HEIGHT);
int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
params.setPreviewFpsRange(range[0], range[1]);
params.setPreviewFormat(SrsEncoder.VFORMAT);
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
if (!params.getSupportedFocusModes().isEmpty()) {
params.setFocusMode(params.getSupportedFocusModes().get(0));
}
mCamera.setParameters(params);
mCamera.setDisplayOrientation(mPreviewRotation);
mCamera.addCallbackBuffer(mYuvFrameBuffer);
mCamera.setPreviewCallbackWithBuffer(this);
try {
mCamera.setPreviewDisplay(mCameraView.getHolder());
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
}
private void stopCamera() {
if (mCamera != null) {
// need to SET NULL CB before stop preview!!!
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
private void onGetYuvFrame(byte[] data) {
// Calculate YUV sampling FPS
if (videoFrameCount == 0) {
lastTimeMillis = System.nanoTime() / 1000000;
videoFrameCount++;
} else {
if (++videoFrameCount >= 48) {
long diffTimeMillis = System.nanoTime() / 1000000 - lastTimeMillis;
Log.i(TAG, String.format("Sampling fps: %f", (double) videoFrameCount * 1000 / diffTimeMillis));
videoFrameCount = 0;
}
}
mEncoder.onGetYuvFrame(data);
}
@Override
public void onPreviewFrame(byte[] data, Camera c) {
onGetYuvFrame(data);
c.addCallbackBuffer(mYuvFrameBuffer);
}
private void onGetPcmFrame(byte[] pcmBuffer, int size) {
mEncoder.onGetPcmFrame(pcmBuffer, size);
protected void onResume() {
super.onResume();
final Button btn = (Button) findViewById(R.id.publish);
btn.setEnabled(true);
mPublisher.resumeRecord();
}
 
private void startAudio() {
if (mic != null) {
return;
}
int bufferSize = 2 * AudioRecord.getMinBufferSize(SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT);
mic = new AudioRecord(MediaRecorder.AudioSource.MIC, SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT, bufferSize);
mic.startRecording();
byte pcmBuffer[] = new byte[4096];
while (aloop && !Thread.interrupted()) {
int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
if (size <= 0) {
Log.e(TAG, "***** audio ignored, no data to read.");
break;
}
onGetPcmFrame(pcmBuffer, size);
}
@Override
protected void onPause() {
super.onPause();
mPublisher.pauseRecord();
}
 
private void stopAudio() {
aloop = false;
if (aworker != null) {
Log.i(TAG, "stop audio worker thread");
aworker.interrupt();
try {
aworker.join();
} catch (InterruptedException e) {
e.printStackTrace();
aworker.interrupt();
}
aworker = null;
}
if (mic != null) {
mic.setRecordPositionUpdateListener(null);
mic.stop();
mic.release();
mic = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
mPublisher.stopPublish();
mPublisher.stopRecord();
}
 
private void startEncoder() {
if (!mEncoder.start()) {
return;
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
mPublisher.setPreviewRotation(90);
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
mPublisher.setPreviewRotation(0);
}
startCamera();
aworker = new Thread(new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
startAudio();
}
});
aloop = true;
aworker.start();
}
private void stopEncoder() {
stopAudio();
stopCamera();
mEncoder.stop();
}
private static int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
expectedFps *= 1000;
int[] closestRange = fpsRanges.get(0);
int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
for (int[] range : fpsRanges) {
if (range[0] <= expectedFps && range[1] >= expectedFps) {
int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
if (curMeasure < measure) {
closestRange = range;
measure = curMeasure;
}
}
mPublisher.stopEncode();
mPublisher.stopRecord();
btnRecord.setText("record");
mPublisher.setScreenOrientation(newConfig.orientation);
if (btnPublish.getText().toString().contentEquals("stop")) {
mPublisher.startEncode();
}
return closestRange;
}
 
private static String getRandomAlphaString(int length) {
Loading
Loading
@@ -508,65 +336,4 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback, Ca
}
return sb.toString();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged");
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
Log.d(TAG, "surfaceCreated");
if (mCamera != null) {
try {
mCamera.setPreviewDisplay(mCameraView.getHolder());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
Log.d(TAG, "surfaceDestroyed");
}
@Override
protected void onResume() {
super.onResume();
final Button btn = (Button) findViewById(R.id.publish);
btn.setEnabled(true);
mp4Muxer.resume();
}
@Override
protected void onPause() {
super.onPause();
mp4Muxer.pause();
}
@Override
protected void onDestroy() {
super.onDestroy();
stopEncoder();
flvMuxer.stop();
mp4Muxer.stop();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
mPreviewRotation = 90;
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
mPreviewRotation = 0;
}
stopEncoder();
mp4Muxer.stop();
btnRecord.setText("record");
mEncoder.setScreenOrientation(newConfig.orientation);
if (btnPublish.getText().toString().contentEquals("stop")) {
startEncoder();
}
}
}
package net.ossrs.yasea;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
Loading
Loading
@@ -20,22 +19,21 @@ public class SrsEncoder {
public static final String VCODEC = "video/avc";
public static final String ACODEC = "audio/mp4a-latm";
public static final int VPREV_WIDTH = 1280;
public static final int VPREV_HEIGHT = 720;
public static final int VOUT_WIDTH = 384;
public static final int VOUT_HEIGHT = 640;
public static int vOutWidth = VOUT_WIDTH; // Note: the stride of resolution must be set as 16x for hard encoding with some chip like MTK
public static int vOutHeight = VOUT_HEIGHT; // Since Y component is quadruple size as U and V component, the stride must be set as 32x
public static final int VBITRATE = 500 * 1000; // 500kbps
public static String x264Preset = "veryfast";
public static int vPrevWidth = 1280;
public static int vPrevHeight = 720;
public static int vPortraitWidth = 384;
public static int vPortraitHeight = 640;
public static int vOutWidth = 384; // Note: the stride of resolution must be set as 16x for hard encoding with some chip like MTK
public static int vOutHeight = 640; // Since Y component is quadruple size as U and V component, the stride must be set as 32x
public static int vBitrate = 500 * 1000; // 500kbps
public static final int VFPS = 24;
public static final int VGOP = 48;
public static final int VFORMAT = ImageFormat.NV21;
public static final int ASAMPLERATE = 44100;
public static final int ACHANNEL = AudioFormat.CHANNEL_IN_STEREO;
public static final int AFORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int ABITRATE = 32 * 1000; // 32kbps
private volatile int mOrientation = Configuration.ORIENTATION_PORTRAIT;
private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
private SrsFlvMuxer flvMuxer;
private SrsMp4Muxer mp4Muxer;
Loading
Loading
@@ -69,14 +67,23 @@ public class SrsEncoder {
// NV16 -> YUV422SP yyyy uv uv
// YUY2 -> YUV422SP yuyv yuyv
public SrsEncoder(SrsFlvMuxer flvMuxer, SrsMp4Muxer mp4Muxer) {
public SrsEncoder() {
mVideoColorFormat = chooseVideoEncoder();
}
public void setFlvMuxer(SrsFlvMuxer flvMuxer) {
this.flvMuxer = flvMuxer;
this.mp4Muxer = mp4Muxer;
}
mVideoColorFormat = chooseVideoEncoder();
public void setMp4Muxer(SrsMp4Muxer mp4Muxer) {
this.mp4Muxer = mp4Muxer;
}
public boolean start() {
if (flvMuxer == null || mp4Muxer == null) {
return false;
}
// the referent PTS for video and audio encoder.
mPresentTimeUs = System.nanoTime() / 1000;
Loading
Loading
@@ -91,13 +98,13 @@ public class SrsEncoder {
setEncoderResolution(vOutWidth, vOutHeight);
setEncoderFps(VFPS);
setEncoderGop(VGOP);
// Unfortunately for some android phone, the output fps is less than 10 limited by the
// Unfortunately for some android phone, the output fps is less than 10 lSrsted by the
// capacity of poor cheap chips even with x264. So for the sake of quick appearance of
// the first picture on the player, a spare lower GOP value is suggested. But note that
// lower GOP will produce more I frames and therefore more streaming data flow.
// setEncoderGop(15);
setEncoderBitrate(VBITRATE);
setEncoderPreset("veryfast");
setEncoderBitrate(vBitrate);
setEncoderPreset(x264Preset);
if (useSoftEncoder && !openSoftEncoder()) {
return false;
Loading
Loading
@@ -139,7 +146,7 @@ public class SrsEncoder {
MediaFormat videoFormat = MediaFormat.createVideoFormat(VCODEC, vOutWidth, vOutHeight);
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mVideoColorFormat);
videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, VBITRATE);
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, vBitrate);
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, VFPS);
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VGOP / VFPS);
vencoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Loading
Loading
@@ -189,16 +196,65 @@ public class SrsEncoder {
useSoftEncoder = false;
}
public boolean isSoftEncoder() {
return useSoftEncoder;
}
public void setPreviewResolution(int width, int height) {
vPrevWidth = width;
vPrevHeight = height;
}
public void setPortraitResolution(int width, int height) {
vOutWidth = width;
vOutHeight = height;
vPortraitWidth = width;
vPortraitHeight = height;
}
public void setLandscapeResolution(int width, int height) {
vOutWidth = width;
vOutHeight = height;
vPortraitWidth = height;
vPortraitHeight = width;
}
public void setVideoHDMode() {
vBitrate = 1200 * 1000; // 1200 kbps
x264Preset = "veryfast";
}
public void setVideoSmoothMode() {
vBitrate = 500 * 1000; // 500 kbps
x264Preset = "superfast";
}
public int getPreviewWidth() {
return vPrevWidth;
}
public int getPreviewHeight() {
return vPrevHeight;
}
public int getOutputWidth() {
return vOutWidth;
}
public int getOutputHeight() {
return vOutHeight;
}
public void setScreenOrientation(int orientation) {
mOrientation = orientation;
if (mOrientation == Configuration.ORIENTATION_PORTRAIT) {
vOutWidth = VOUT_WIDTH;
vOutHeight = VOUT_HEIGHT;
vOutWidth = vPortraitWidth;
vOutHeight = vPortraitHeight;
} else if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) {
vOutWidth = VOUT_HEIGHT;
vOutHeight = VOUT_WIDTH;
vOutWidth = vPortraitHeight;
vOutHeight = vPortraitWidth;
}
// Note: the stride of resolution must be set as 16x for hard encoding with some chip like MTK
// Since Y component is quadruple size as U and V component, the stride must be set as 32x
if (!useSoftEncoder && vOutWidth % 32 != 0 || vOutHeight % 32 != 0) {
Loading
Loading
@@ -239,7 +295,6 @@ public class SrsEncoder {
vebi.offset = 0;
vebi.size = es.length;
vebi.presentationTimeUs = pts;
//vebi.presentationTimeUs = System.nanoTime() / 1000 - mPresentTimeUs;
vebi.flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
onEncodedAnnexbFrame(bb, vebi);
}
Loading
Loading
@@ -324,18 +379,18 @@ public class SrsEncoder {
if (mCameraFaceFront) {
switch (mVideoColorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
return NV21ToI420(data, VPREV_WIDTH, VPREV_HEIGHT, true, 270);
return NV21ToI420(data, vPrevWidth, vPrevHeight, true, 270);
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
return NV21ToNV12(data, VPREV_WIDTH, VPREV_HEIGHT, true, 270);
return NV21ToNV12(data, vPrevWidth, vPrevHeight, true, 270);
default:
throw new IllegalStateException("Unsupported color format!");
}
} else {
switch (mVideoColorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
return NV21ToI420(data, VPREV_WIDTH, VPREV_HEIGHT, false, 90);
return NV21ToI420(data, vPrevWidth, vPrevHeight, false, 90);
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
return NV21ToNV12(data, VPREV_WIDTH, VPREV_HEIGHT, false, 90);
return NV21ToNV12(data, vPrevWidth, vPrevHeight, false, 90);
default:
throw new IllegalStateException("Unsupported color format!");
}
Loading
Loading
@@ -346,18 +401,18 @@ public class SrsEncoder {
if (mCameraFaceFront) {
switch (mVideoColorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
return NV21ToI420(data, VPREV_WIDTH, VPREV_HEIGHT, true, 0);
return NV21ToI420(data, vPrevWidth, vPrevHeight, true, 0);
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
return NV21ToNV12(data, VPREV_WIDTH, VPREV_HEIGHT, true, 0);
return NV21ToNV12(data, vPrevWidth, vPrevHeight, true, 0);
default:
throw new IllegalStateException("Unsupported color format!");
}
} else {
switch (mVideoColorFormat) {
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
return NV21ToI420(data, VPREV_WIDTH, VPREV_HEIGHT, false, 0);
return NV21ToI420(data, vPrevWidth, vPrevHeight, false, 0);
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
return NV21ToNV12(data, VPREV_WIDTH, VPREV_HEIGHT, false, 0);
return NV21ToNV12(data, vPrevWidth, vPrevHeight, false, 0);
default:
throw new IllegalStateException("Unsupported color format!");
}
Loading
Loading
@@ -366,17 +421,17 @@ public class SrsEncoder {
private void swPortraitYuvFrame(byte[] data, long pts) {
if (mCameraFaceFront) {
NV21SoftEncode(data, VPREV_WIDTH, VPREV_HEIGHT, true, 270, pts);
NV21SoftEncode(data, vPrevWidth, vPrevHeight, true, 270, pts);
} else {
NV21SoftEncode(data, VPREV_WIDTH, VPREV_HEIGHT, false, 90, pts);
NV21SoftEncode(data, vPrevWidth, vPrevHeight, false, 90, pts);
}
}
private void swLandscapeYuvFrame(byte[] data, long pts) {
if (mCameraFaceFront) {
NV21SoftEncode(data, VPREV_WIDTH, VPREV_HEIGHT, true, 0, pts);
NV21SoftEncode(data, vPrevWidth, vPrevHeight, true, 0, pts);
} else {
NV21SoftEncode(data, VPREV_WIDTH, VPREV_HEIGHT, false, 0, pts);
NV21SoftEncode(data, vPrevWidth, vPrevHeight, false, 0, pts);
}
}
Loading
Loading
package net.ossrs.yasea;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import net.ossrs.yasea.rtmp.RtmpPublisher;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* Created by Leo Ma on 2016/7/25.
*/
public class SrsPublisher implements SurfaceHolder.Callback, Camera.PreviewCallback {
private static final String TAG = "SrsPublisher";
private AudioRecord mic;
private boolean aloop = false;
private Thread aworker;
private SurfaceView mCameraView;
private Camera mCamera;
private int videoFrameCount;
private long lastTimeMillis;
private int mPreviewRotation = 90;
private int mCamId = Camera.getNumberOfCameras() - 1;
private double mSamplingFps;
private byte[] mYuvPreviewFrame;
private SrsFlvMuxer mFlvMuxer;
private SrsMp4Muxer mMp4Muxer;
private SrsEncoder mEncoder = new SrsEncoder();
public SrsPublisher() {
}
public void startEncode() {
if (!mEncoder.start()) {
return;
}
startCamera();
aworker = new Thread(new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
startAudio();
}
});
aloop = true;
aworker.start();
}
public void stopEncode() {
stopAudio();
stopCamera();
mEncoder.stop();
}
public void startPublish(String rtmpUrl) {
if (mFlvMuxer != null) {
try {
mFlvMuxer.start(rtmpUrl);
} catch (IOException e) {
e.printStackTrace();
return;
}
mFlvMuxer.setVideoResolution(mEncoder.getOutputWidth(), mEncoder.getOutputHeight());
startEncode();
}
}
public void stopPublish() {
if (mFlvMuxer != null) {
stopEncode();
mFlvMuxer.stop();
}
}
public void startRecord(String recPath) {
if (mMp4Muxer != null) {
try {
mMp4Muxer.record(new File(recPath));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stopRecord() {
if (mMp4Muxer != null) {
mMp4Muxer.stop();
}
}
public void pauseRecord() {
if (mMp4Muxer != null) {
mMp4Muxer.pause();
}
}
public void resumeRecord() {
if (mMp4Muxer != null) {
mMp4Muxer.resume();
}
}
public void swithToSoftEncoder() {
mEncoder.swithToSoftEncoder();
}
public void swithToHardEncoder() {
mEncoder.swithToHardEncoder();
}
public boolean isSoftEncoder() {
return mEncoder.isSoftEncoder();
}
public double getmSamplingFps() {
return mSamplingFps;
}
public int getCamraId() {
return mCamId;
}
public int getNumberOfCameras() {
return mCamera != null ? mCamera.getNumberOfCameras() : -1;
}
public void setPreviewResolution(int width, int height) {
mEncoder.setPreviewResolution(width, height);
}
public void setOutputResolution(int width, int height) {
mEncoder.setPortraitResolution(width, height);
}
public void setScreenOrientation(int orientation) {
mEncoder.setScreenOrientation(orientation);
}
public void setPreviewRotation(int rotation) {
mPreviewRotation = rotation;
}
public void setVideoHDMode() {
mEncoder.setVideoHDMode();
}
public void setVideoSmoothMode() {
mEncoder.setVideoSmoothMode();
}
public void switchCameraFace(int id) {
mCamId = id;
stopCamera();
mEncoder.swithCameraFace();
startCamera();
}
private void startCamera() {
if (mCamera != null) {
return;
}
if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {
return;
}
mCamera = Camera.open(mCamId);
Camera.Parameters params = mCamera.getParameters();
Camera.Size size = mCamera.new Size(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());
if (!params.getSupportedPreviewSizes().contains(size) || !params.getSupportedPictureSizes().contains(size)) {
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
new IllegalArgumentException(String.format("Unsupported resolution %dx%d", size.width, size.height)));
}
mYuvPreviewFrame = new byte[mEncoder.getPreviewWidth() * mEncoder.getPreviewHeight() * 3 / 2];
/***** set parameters *****/
//params.set("orientation", "portrait");
//params.set("orientation", "landscape");
//params.setRotation(90);
params.setPictureSize(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());
params.setPreviewSize(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());
int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
params.setPreviewFpsRange(range[0], range[1]);
params.setPreviewFormat(ImageFormat.NV21);
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
if (!params.getSupportedFocusModes().isEmpty()) {
params.setFocusMode(params.getSupportedFocusModes().get(0));
}
mCamera.setParameters(params);
mCamera.setDisplayOrientation(mPreviewRotation);
mCamera.addCallbackBuffer(mYuvPreviewFrame);
mCamera.setPreviewCallbackWithBuffer(this);
try {
if (mCameraView != null){
mCamera.setPreviewDisplay(mCameraView.getHolder());
}
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
}
private static int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
expectedFps *= 1000;
int[] closestRange = fpsRanges.get(0);
int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
for (int[] range : fpsRanges) {
if (range[0] <= expectedFps && range[1] >= expectedFps) {
int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
if (curMeasure < measure) {
closestRange = range;
measure = curMeasure;
}
}
}
return closestRange;
}
private void stopCamera() {
if (mCamera != null) {
// need to SET NULL CB before stop preview!!!
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
private void onGetYuvFrame(byte[] data) {
// Calculate YUV sampling FPS
if (videoFrameCount == 0) {
lastTimeMillis = System.nanoTime() / 1000000;
videoFrameCount++;
} else {
if (++videoFrameCount >= 48) {
long diffTimeMillis = System.nanoTime() / 1000000 - lastTimeMillis;
mSamplingFps = (double) videoFrameCount * 1000 / diffTimeMillis;
videoFrameCount = 0;
}
}
mEncoder.onGetYuvFrame(data);
}
private void startAudio() {
if (mic != null) {
return;
}
int bufferSize = 2 * AudioRecord.getMinBufferSize(SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, AudioFormat.ENCODING_PCM_16BIT);
mic = new AudioRecord(MediaRecorder.AudioSource.MIC, SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
mic.startRecording();
byte pcmBuffer[] = new byte[4096];
while (aloop && !Thread.interrupted()) {
int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
if (size <= 0) {
break;
}
mEncoder.onGetPcmFrame(pcmBuffer, size);
}
}
private void stopAudio() {
aloop = false;
if (aworker != null) {
aworker.interrupt();
try {
aworker.join();
} catch (InterruptedException e) {
e.printStackTrace();
aworker.interrupt();
}
aworker = null;
}
if (mic != null) {
mic.setRecordPositionUpdateListener(null);
mic.stop();
mic.release();
mic = null;
}
}
public void setPublishEventHandler(RtmpPublisher.EventHandler handler) {
mFlvMuxer = new SrsFlvMuxer(handler);
mEncoder.setFlvMuxer(mFlvMuxer);
}
public void setRecordEventHandler(SrsMp4Muxer.EventHandler handler) {
mMp4Muxer = new SrsMp4Muxer(handler);
mEncoder.setMp4Muxer(mMp4Muxer);
}
public void setSurfaceView(SurfaceView surfaceView) {
mCameraView = surfaceView;
mCameraView.getHolder().addCallback(this);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
onGetYuvFrame(data);
camera.addCallbackBuffer(mYuvPreviewFrame);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
if (mCamera != null) {
try {
mCamera.setPreviewDisplay(mCameraView.getHolder());
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment