diff --git a/README.asciidoc b/README.asciidoc index 70d60ea..222b0fd 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -30,13 +30,12 @@ KSY Streamer Android SDK是金山云推出的 Android 平台上使用的软件 * [x] **自定义音频滤镜 (new)** * [x] 背景音乐功能, 支持本地mp3, aac等格式 * [x] 支持图片及时间戳水印 +* [x] 开放KSYStreamer类的实现,开发者可自行组装各个模块 (new) * [x] https://github.com/ksvc/KSYRTCLive_Android[连麦] * [x] https://github.com/ksvc/KSYDiversityLive_Android/tree/master/KSYScreenStreamer[录屏直播], http://www.bilibili.com/video/av7038614[录屏直播效果] === 即将支持 -* [ ] 开放KSYStreamer类的实现,开发者可自行组装各个模块 * [ ] 画中画 -* [ ] 录屏推流 * [ ] 低延迟耳返 * [ ] 音频升降调 diff --git a/src/KSYStreamer.java b/src/KSYStreamer.java new file mode 100755 index 0000000..95ac9fc --- /dev/null +++ b/src/KSYStreamer.java @@ -0,0 +1,1623 @@ +package com.ksyun.media.streamer.kit; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.text.TextUtils; +import android.util.Log; +import android.view.TextureView; + +import com.ksyun.media.streamer.capture.AudioCapture; +import com.ksyun.media.streamer.capture.AudioPlayerCapture; +import com.ksyun.media.streamer.capture.CameraCapture; +import com.ksyun.media.streamer.capture.WaterMarkCapture; +import com.ksyun.media.streamer.encoder.AVCodecAudioEncoder; +import com.ksyun.media.streamer.encoder.AudioEncodeFormat; +import com.ksyun.media.streamer.encoder.AudioEncoderMgt; +import com.ksyun.media.streamer.encoder.Encoder; +import com.ksyun.media.streamer.encoder.MediaCodecAudioEncoder; +import com.ksyun.media.streamer.encoder.VideoEncodeFormat; +import com.ksyun.media.streamer.encoder.VideoEncoderMgt; +import com.ksyun.media.streamer.filter.audio.AudioFilterMgt; +import com.ksyun.media.streamer.filter.audio.AudioMixer; +import com.ksyun.media.streamer.filter.audio.AudioPreview; +import com.ksyun.media.streamer.filter.audio.AudioResampleFilter; +import com.ksyun.media.streamer.filter.imgtex.ImgTexFilterMgt; +import com.ksyun.media.streamer.filter.imgtex.ImgTexMixer; +import com.ksyun.media.streamer.filter.imgtex.ImgTexScaleFilter; +import com.ksyun.media.streamer.framework.AVConst; +import com.ksyun.media.streamer.framework.AudioBufFormat; +import com.ksyun.media.streamer.logstats.StatsConstant; +import com.ksyun.media.streamer.logstats.StatsLogReport; +import com.ksyun.media.streamer.publisher.RtmpPublisher; +import com.ksyun.media.streamer.util.gles.GLRender; + + +/** + * All in one streamer class. + */ +public class KSYStreamer { + + private static final String TAG = "KSYStreamer"; + private static final boolean DEBUG = false; + + private Context mContext; + + private String mUri; + private int mScreenRenderWidth = 0; + private int mScreenRenderHeight = 0; + private int mPreviewResolution = StreamerConstants.VIDEO_RESOLUTION_360P; + private int mPreviewWidth = 0; + private int mPreviewHeight = 0; + private float mPreviewFps = 0; + private int mTargetResolution = StreamerConstants.VIDEO_RESOLUTION_360P; + private int mTargetWidth = 0; + private int mTargetHeight = 0; + private float mTargetFps = 0; + private float mIFrameInterval = 3.0f; + private int mRotateDegrees = 0; + private int mMaxVideoBitrate = 800 * 1000; + private int mInitVideoBitrate = 600 * 1000; + private int mMinVideoBitrate = 200 * 1000; + private boolean mAutoAdjustVideoBitrate = true; + private int mAudioBitrate = 48 * 1000; + private int mAudioSampleRate = 44100; + private int mAudioChannels = 1; + + private boolean mFrontCameraMirror = false; + private boolean mEnableStreamStatModule = true; + private int mCameraFacing = CameraCapture.FACING_FRONT; + + private boolean mIsRecording = false; + private boolean mIsAudioOnly = false; + private boolean mIsAudioPreviewing = false; + private boolean mDelayedStartCameraPreview = false; + private boolean mEnableDebugLog = false; + private boolean mEnableAudioMix = false; + + // compat members + private KSYStreamerConfig mConfig; + + private OnInfoListener mOnInfoListener; + private OnErrorListener mOnErrorListener; + + private GLRender mGLRender; + private CameraCapture mCameraCapture; + private WaterMarkCapture mWaterMarkCapture; + private ImgTexScaleFilter mImgTexScaleFilter; + private ImgTexMixer mImgTexMixer; + private ImgTexFilterMgt mImgTexFilterMgt; + private AudioCapture mAudioCapture; + private VideoEncoderMgt mVideoEncoderMgt; + private AudioEncoderMgt mAudioEncoderMgt; + private RtmpPublisher mRtmpPublisher; + + private AudioResampleFilter mAudioResampleFilter; + private AudioFilterMgt mAudioFilterMgt; + private AudioPlayerCapture mAudioPlayerCapture; + private AudioMixer mAudioMixer; + private AudioPreview mAudioPreview; + + public KSYStreamer(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null!"); + } + mContext = context.getApplicationContext(); + initModules(); + } + + /** + * Set params to KSYStreamer with {@link KSYStreamerConfig}. + * + * @param config streamer params + * @deprecated + */ + @Deprecated + public void setConfig(KSYStreamerConfig config) { + if (config == null || mContext == null) { + throw new IllegalStateException("method invoking failed " + + "context == null or config == null!"); + } + + mConfig = config; + setUrl(config.getUrl()); + setPreviewResolution(config.getVideoResolution()); + setTargetResolution(config.getVideoResolution()); + setPreviewFps(config.getFrameRate()); + setTargetFps(config.getFrameRate()); + setIFrameInterval(config.getIFrameIntervalSec()); + setRotateDegrees(config.getDefaultLandscape() ? 90 : 0); + if (config.isAutoAdjustBitrate()) { + setVideoBitrate(config.getInitAverageVideoBitrate() * 1000, + config.getMaxAverageVideoBitrate() * 1000, + config.getMinAverageVideoBitrate() * 1000); + } else { + setVideoBitrate(config.getInitAverageVideoBitrate() * 1000); + } + setAudioSampleRate(config.getAudioSampleRate()); + setAudioChannels(config.getAudioChannels()); + setAudioBitrate(config.getAudioBitrate() * 1000); + setEncodeMethod(config.getEncodeMethod()); + setFrontCameraMirror(config.isFrontCameraMirror()); + setEnableStreamStatModule(config.isEnableStreamStatModule()); + setCameraFacing(config.getDefaultFrontCamera() ? + CameraCapture.FACING_FRONT : CameraCapture.FACING_BACK); + } + + private void initModules() { + // Init GLRender for gpu render + mGLRender = new GLRender(); + + // Watermark capture + mWaterMarkCapture = new WaterMarkCapture(mGLRender); + + // Camera preview + mCameraCapture = new CameraCapture(mContext, mGLRender); + mImgTexScaleFilter = new ImgTexScaleFilter(mGLRender); + mImgTexFilterMgt = new ImgTexFilterMgt(); + mImgTexMixer = new ImgTexMixer(mGLRender); + mImgTexMixer.setIsPreviewer(true); + mCameraCapture.mImgTexSrcPin.connect(mImgTexScaleFilter.getSinkPin()); + mImgTexScaleFilter.getSrcPin().connect(mImgTexFilterMgt.getSinkPin()); + mImgTexFilterMgt.getSrcPin().connect(mImgTexMixer.getSinkPin(0)); + mWaterMarkCapture.mLogoTexSrcPin.connect(mImgTexMixer.getSinkPin(1)); + mWaterMarkCapture.mTimeTexSrcPin.connect(mImgTexMixer.getSinkPin(2)); + + // Audio preview + mAudioPlayerCapture = new AudioPlayerCapture(mContext); + mAudioCapture = new AudioCapture(); + mAudioResampleFilter = new AudioResampleFilter(); + mAudioFilterMgt = new AudioFilterMgt(); + mAudioMixer = new AudioMixer(); + mAudioPreview = new AudioPreview(); + mAudioCapture.mAudioBufSrcPin.connect(mAudioResampleFilter.getSinkPin()); + mAudioResampleFilter.getSrcPin().connect(mAudioFilterMgt.getSinkPin()); + mAudioFilterMgt.getSrcPin().connect(mAudioMixer.getSinkPin(0)); + if (mEnableAudioMix) { + mAudioPlayerCapture.mSrcPin.connect(mAudioMixer.getSinkPin(1)); + } + mAudioMixer.getSrcPin().connect(mAudioPreview.mSinkPin); + + // encoder + mVideoEncoderMgt = new VideoEncoderMgt(mGLRender); + mAudioEncoderMgt = new AudioEncoderMgt(); + mWaterMarkCapture.mLogoBufSrcPin.connect(mVideoEncoderMgt.getImgBufMixer().getSinkPin(1)); + mWaterMarkCapture.mTimeBufSrcPin.connect(mVideoEncoderMgt.getImgBufMixer().getSinkPin(2)); + mImgTexMixer.getSrcPin().connect(mVideoEncoderMgt.getImgTexSinkPin()); + mCameraCapture.mImgBufSrcPin.connect(mVideoEncoderMgt.getImgBufSinkPin()); + mAudioMixer.getSrcPin().connect(mAudioEncoderMgt.getSinkPin()); + + // publisher + mRtmpPublisher = new RtmpPublisher(); + mAudioEncoderMgt.getSrcPin().connect(mRtmpPublisher.mAudioSink); + mVideoEncoderMgt.getSrcPin().connect(mRtmpPublisher.mVideoSink); + + // stats + StatsLogReport.getInstance().initLogReport(mContext); + + // set listeners + mAudioCapture.setAudioCaptureListener(new AudioCapture.OnAudioCaptureListener() { + @Override + public void onStatusChanged(int status) { + } + + @Override + public void onError(int errorCode) { + Log.e(TAG, "AudioCapture error: " + errorCode); + int what; + switch (errorCode) { + case AudioCapture.AUDIO_START_FAILED: + what = StreamerConstants.KSY_STREAMER_AUDIO_RECORDER_ERROR_START_FAILED; + break; + case AudioCapture.AUDIO_ERROR_UNKNOWN: + default: + what = StreamerConstants.KSY_STREAMER_AUDIO_RECORDER_ERROR_UNKNOWN; + break; + } + if (mOnErrorListener != null) { + mOnErrorListener.onError(what, 0, 0); + } + } + }); + + mCameraCapture.setOnCameraCaptureListener(new CameraCapture.OnCameraCaptureListener() { + @Override + public void onStarted() { + Log.d(TAG, "CameraCapture ready"); + if (mOnInfoListener != null) { + mOnInfoListener.onInfo(StreamerConstants.KSY_STREAMER_CAMERA_INIT_DONE, 0, 0); + } + } + + @Override + public void onFacingChanged(int facing) { + mCameraFacing = facing; + updateFrontMirror(); + } + + @Override + public void onError(int err) { + Log.e(TAG, "CameraCapture error: " + err); + int what; + switch (err) { + case CameraCapture.CAMERA_ERROR_START_FAILED: + what = StreamerConstants.KSY_STREAMER_CAMERA_ERROR_START_FAILED; + break; + case CameraCapture.CAMERA_ERROR_SERVER_DIED: + what = StreamerConstants.KSY_STREAMER_CAMERA_ERROR_SERVER_DIED; + break; + case CameraCapture.CAMERA_ERROR_UNKNOWN: + default: + what = StreamerConstants.KSY_STREAMER_CAMERA_ERROR_UNKNOWN; + break; + } + if (mOnErrorListener != null) { + mOnErrorListener.onError(what, 0, 0); + } + } + }); + + Encoder.EncoderListener encoderListener = new Encoder.EncoderListener() { + @Override + public void onError(Encoder encoder, int err) { + if (err != 0) { + stopStream(); + } + + boolean isVideo = true; + if (encoder instanceof MediaCodecAudioEncoder || + encoder instanceof AVCodecAudioEncoder) { + isVideo = false; + } + + int what; + switch (err) { + case Encoder.ENCODER_ERROR_UNSUPPORTED: + what = isVideo ? + StreamerConstants.KSY_STREAMER_VIDEO_ENCODER_ERROR_UNSUPPORTED : + StreamerConstants.KSY_STREAMER_AUDIO_ENCODER_ERROR_UNSUPPORTED; + break; + case Encoder.ENCODER_ERROR_UNKNOWN: + default: + what = isVideo ? + StreamerConstants.KSY_STREAMER_VIDEO_ENCODER_ERROR_UNKNOWN : + StreamerConstants.KSY_STREAMER_AUDIO_ENCODER_ERROR_UNKNOWN; + break; + } + if (mOnErrorListener != null) { + mOnErrorListener.onError(what, 0, 0); + } + } + }; + mVideoEncoderMgt.setEncoderListener(encoderListener); + mAudioEncoderMgt.setEncoderListener(encoderListener); + + mRtmpPublisher.setRtmpPubListener(new RtmpPublisher.RtmpPubListener() { + @Override + public void onInfo(int type, long msg) { + switch (type) { + case RtmpPublisher.INFO_CONNECTED: + mAudioEncoderMgt.getEncoder().start(); + if (mOnInfoListener != null) { + mOnInfoListener.onInfo( + StreamerConstants.KSY_STREAMER_OPEN_STREAM_SUCCESS, 0, 0); + } + break; + case RtmpPublisher.INFO_AUDIO_HEADER_GOT: + if (!mIsAudioOnly) { + // start video encoder after audio header got + mVideoEncoderMgt.getEncoder().start(); + } + break; + case RtmpPublisher.INFO_PACKET_SEND_SLOW: + Log.i(TAG, "packet send slow, delayed " + msg + "ms"); + if (mOnInfoListener != null) { + mOnInfoListener.onInfo( + StreamerConstants.KSY_STREAMER_FRAME_SEND_SLOW, + (int) msg, 0); + } + break; + case RtmpPublisher.INFO_EST_BW_RAISE: + if (mIsAudioOnly) { + break; + } + if (mAutoAdjustVideoBitrate) { + Log.d(TAG, "Raise video bitrate to " + msg); + mVideoEncoderMgt.getEncoder().adjustBitrate((int) msg); + } + if (mOnInfoListener != null) { + mOnInfoListener.onInfo( + StreamerConstants.KSY_STREAMER_EST_BW_RAISE, (int) msg, 0); + } + break; + case RtmpPublisher.INFO_EST_BW_DROP: + if (mIsAudioOnly) { + break; + } + if (mAutoAdjustVideoBitrate) { + Log.d(TAG, "Drop video bitrate to " + msg); + mVideoEncoderMgt.getEncoder().adjustBitrate((int) msg); + } + if (mOnInfoListener != null) { + mOnInfoListener.onInfo( + StreamerConstants.KSY_STREAMER_EST_BW_DROP, (int) msg, 0); + } + break; + default: + break; + } + } + + @Override + public void onError(int err, long msg) { + Log.e(TAG, "RtmpPub err=" + err); + if (err != 0) { + stopStream(); + } + + if (mOnErrorListener != null) { + int status; + switch (err) { + case RtmpPublisher.ERROR_CONNECT_BREAKED: + status = StreamerConstants.KSY_STREAMER_ERROR_CONNECT_BREAKED; + break; + case RtmpPublisher.ERROR_DNS_PARSE_FAILED: + status = StreamerConstants.KSY_STREAMER_ERROR_DNS_PARSE_FAILED; + break; + case RtmpPublisher.ERROR_CONNECT_FAILED: + status = StreamerConstants.KSY_STREAMER_ERROR_CONNECT_FAILED; + break; + case RtmpPublisher.ERROR_PUBLISH_FAILED: + status = StreamerConstants.KSY_STREAMER_ERROR_PUBLISH_FAILED; + break; + case RtmpPublisher.ERROR_AV_ASYNC_ERROR: + status = StreamerConstants.KSY_STREAMER_ERROR_AV_ASYNC; + break; + default: + status = StreamerConstants.KSY_STREAMER_ERROR_PUBLISH_FAILED; + break; + } + mOnErrorListener.onError(status, (int) msg, 0); + } + } + }); + } + + /** + * Get {@link GLRender} instance. + * + * @return GLRender instance. + */ + public GLRender getGLRender() { + return mGLRender; + } + + /** + * Get {@link CameraCapture} module instance. + * + * @return CameraCapture instance. + */ + public CameraCapture getCameraCapture() { + return mCameraCapture; + } + + /** + * Get {@link AudioCapture} module instance. + * + * @return AudioCapture instance. + */ + public AudioCapture getAudioCapture() { + return mAudioCapture; + } + + /** + * Get {@link ImgTexFilterMgt} instance to manage GPU filters. + * + * @return ImgTexFilterMgt instance. + */ + public ImgTexFilterMgt getImgTexFilterMgt() { + return mImgTexFilterMgt; + } + + /** + * Get {@link AudioFilterMgt} instance to manage audio filters. + * + * @return AudioFilterMgt instance + */ + public AudioFilterMgt getAudioFilterMgt() { + return mAudioFilterMgt; + } + + /** + * Get {@link ImgTexMixer} instance which could handle PIP related operations. + * + * @return ImgTexMixer instance. + */ + public ImgTexMixer getImgTexMixer() { + return mImgTexMixer; + } + + /** + * Get {@link VideoEncoderMgt} instance which control video encoders. + * + * @return VideoEncoderMgt instance. + */ + public VideoEncoderMgt getVideoEncoderMgt() { + return mVideoEncoderMgt; + } + + /** + * Get {@link AudioEncoderMgt} instance which control audio encoders. + * + * @return AudioEncoderMgt instance. + */ + public AudioEncoderMgt getAudioEncoderMgt() { + return mAudioEncoderMgt; + } + + /** + * Get {@link AudioPlayerCapture} instance which could handle BGM related operations. + * + * @return AudioPlayerCapture instance + */ + public AudioPlayerCapture getAudioPlayerCapture() { + return mAudioPlayerCapture; + } + + /** + * Get {@link RtmpPublisher} instance which publish encoded a/v frames throw rtmp protocol. + * + * @return RtmpPublisher instance. + */ + public RtmpPublisher getRtmpPublisher() { + return mRtmpPublisher; + } + + /** + * Set GLSurfaceView as camera previewer.
+ * Must set once before the GLSurfaceView created. + * + * @param surfaceView GLSurfaceView to be set. + */ + public void setDisplayPreview(GLSurfaceView surfaceView) { + mGLRender.init(surfaceView); + mGLRender.addListener(mGLRenderListener); + } + + /** + * Set TextureView as camera previewer.
+ * Must set once before the TextureView ready. + * + * @param textureView TextureView to be set. + */ + public void setDisplayPreview(TextureView textureView) { + mGLRender.init(textureView); + mGLRender.addListener(mGLRenderListener); + } + + /** + * Set streaming url.
+ * must set before startStream, must not be null + * The set url would take effect on the next {@link #startStream()} call. + * + * @param url Streaming url to set. + * @throws IllegalArgumentException + */ + public void setUrl(String url) { + if (TextUtils.isEmpty(url)) { + throw new IllegalArgumentException("url can not be null"); + } + mUri = url; + } + + /** + * @param url url streaming to. + * @deprecated Use {@link #setUrl} instead. + */ + @Deprecated + public void updateUrl(String url) { + setUrl(url); + } + + /** + * Set rotate degrees in anti-clockwise of current Activity. + * + * @param degrees Degrees in anti-clockwise, only 0, 90, 180, 270 accepted. + */ + public void setRotateDegrees(int degrees) { + degrees %= 360; + if (degrees % 90 != 0) { + throw new IllegalArgumentException("Invalid rotate degrees"); + } + mRotateDegrees = degrees; + } + + /** + * Set preview resolution.
+ *

+ * The set resolution would take effect on next {@link #startCameraPreview()} + * {@link #startCameraPreview(int)} call.
+ *

+ * The set width and height must not be 0 at same time. + * If one of the params is 0, the other would calculated by the actual preview view size + * to keep the ratio of the preview view. + * + * @param width preview width. + * @param height preview height. + */ + public void setPreviewResolution(int width, int height) { + mPreviewWidth = width; + mPreviewHeight = height; + } + + /** + * Set preview resolution index.
+ *

+ * The set resolution would take effect on next {@link #startCameraPreview()} + * {@link #startCameraPreview(int)} call. + * + * @param idx Resolution index.
+ * @see StreamerConstants#VIDEO_RESOLUTION_360P + * @see StreamerConstants#VIDEO_RESOLUTION_480P + * @see StreamerConstants#VIDEO_RESOLUTION_540P + * @see StreamerConstants#VIDEO_RESOLUTION_720P + */ + public void setPreviewResolution(int idx) { + if (idx < StreamerConstants.VIDEO_RESOLUTION_360P || + idx > StreamerConstants.VIDEO_RESOLUTION_720P) { + throw new IllegalArgumentException("Invalid resolution index"); + } + mPreviewResolution = idx; + } + + /** + * Set preview fps.
+ *

+ * The set fps would take effect on next {@link #startCameraPreview()} + * {@link #startCameraPreview(int)} call.
+ *

+ * The actual preview fps is depend on device, may be different with the set value. + * + * @param fps frame rate to be set. + */ + public void setPreviewFps(float fps) { + mPreviewFps = fps; + if (mTargetFps == 0) { + mTargetFps = mPreviewFps; + } + } + + /** + * Set encode method for both video and audio.
+ * Must not be set while encoding. + * default value:ENCODE_METHOD_SOFTWARE + * + * @param encodeMethod Encode method.
+ * @throws IllegalStateException + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE_COMPAT + * @see StreamerConstants#ENCODE_METHOD_HARDWARE + */ + public void setEncodeMethod(int encodeMethod) { + setVideoEncodeMethod(encodeMethod); + setAudioEncodeMethod(encodeMethod); + } + + /** + * Set encode method for video.
+ * Must not be set while encoding. + * + * @param encodeMethod Encode method.
+ * @throws IllegalStateException + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE_COMPAT + * @see StreamerConstants#ENCODE_METHOD_HARDWARE + */ + public void setVideoEncodeMethod(int encodeMethod) { + if (mIsRecording) { + throw new IllegalStateException("Cannot set encode method while recording"); + } + mVideoEncoderMgt.setEncodeMethod(encodeMethod); + } + + /** + * Get video encode method. + * + * @return video encode method. + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE_COMPAT + * @see StreamerConstants#ENCODE_METHOD_HARDWARE + */ + public int getVideoEncodeMethod() { + return mVideoEncoderMgt.getEncodeMethod(); + } + + /** + * Set encode method for audio.
+ * Must not be set while encoding. + * + * @param encodeMethod Encode method.
+ * @throws IllegalStateException + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE + * @see StreamerConstants#ENCODE_METHOD_HARDWARE + */ + public void setAudioEncodeMethod(int encodeMethod) { + if (mIsRecording) { + throw new IllegalStateException("Cannot set encode method while recording"); + } + mAudioEncoderMgt.setEncodeMethod(encodeMethod); + } + + /** + * Get audio encode method. + * + * @return video encode method. + * @see StreamerConstants#ENCODE_METHOD_SOFTWARE + * @see StreamerConstants#ENCODE_METHOD_HARDWARE + */ + public int getAudioEncodeMethod() { + return mAudioEncoderMgt.getEncodeMethod(); + } + + /** + * Set streaming resolution.
+ *

+ * The set resolution would take effect on next + * {@link #startStream()} call.
+ *

+ * The set width and height must not be 0 at same time. + * If one of the params is 0, the other would calculated by the actual preview view size + * to keep the ratio of the preview view. + * + * @param width streaming width. + * @param height streaming height. + */ + public void setTargetResolution(int width, int height) { + mTargetWidth = width; + mTargetHeight = height; + } + + /** + * Set streaming resolution index.
+ *

+ * The set resolution would take effect on next + * {@link #startStream()} call. + * + * @param idx Resolution index.
+ * @throws IllegalArgumentException + * @see StreamerConstants#VIDEO_RESOLUTION_360P + * @see StreamerConstants#VIDEO_RESOLUTION_480P + * @see StreamerConstants#VIDEO_RESOLUTION_540P + * @see StreamerConstants#VIDEO_RESOLUTION_720P + */ + public void setTargetResolution(int idx) { + if (idx < StreamerConstants.VIDEO_RESOLUTION_360P || + idx > StreamerConstants.VIDEO_RESOLUTION_720P) { + throw new IllegalArgumentException("Invalid resolution index"); + } + mTargetResolution = idx; + } + + /** + * Set streaming fps.
+ *

+ * The set fps would take effect on next + * {@link #startStream()} call.
+ *

+ * If actual preview fps is larger than set value, + * the extra frames will be dropped before encoding, + * and if is smaller than set value, nothing will be done. + * default value : 15 + * + * @param fps frame rate. + * @throws IllegalArgumentException + */ + public void setTargetFps(float fps) { + if (fps <= 0) { + throw new IllegalArgumentException("the fps must > 0"); + } + mTargetFps = fps; + if (mPreviewFps == 0) { + mPreviewFps = mTargetFps; + } + } + + /** + * Set key frames interval in seconds.
+ * Would take effect on next {@link #startStream()} call. + * default value 3.0f + * + * @param iFrameInterval key frame interval in seconds. + * @throws IllegalArgumentException + */ + public void setIFrameInterval(float iFrameInterval) { + if (iFrameInterval <= 0) { + throw new IllegalArgumentException("the IFrameInterval must > 0"); + } + + mIFrameInterval = iFrameInterval; + } + + /** + * Set video bitrate in bps, and disable video bitrate auto adjustment.
+ * Would take effect on next {@link #startStream()} call. + * default value : 600 * 1000 + * + * @param bitrate video bitrate in bps + * @throws IllegalArgumentException + */ + public void setVideoBitrate(int bitrate) { + if (bitrate <= 0) { + throw new IllegalArgumentException("the VideoBitrate must > 0"); + } + mInitVideoBitrate = bitrate; + mAutoAdjustVideoBitrate = false; + StatsLogReport.getInstance().setAutoAdjustVideoBitrate(false); + } + + /** + * Set video bitrate in kbps, and disable video bitrate auto adjustment.
+ * Would take effect on next {@link #startStream()} call. + * + * @param kBitrate video bitrate in kbps + * @throws IllegalArgumentException + */ + public void setVideoKBitrate(int kBitrate) { + setVideoBitrate(kBitrate * 1024); + } + + /** + * Set video init/min/max bitrate in bps, and enable video bitrate auto adjustment.
+ * Would take effect on next {@link #startStream()} call. + * + * @param initVideoBitrate init video bitrate in bps. default value 600 * 1000 + * @param maxVideoBitrate max video bitrate in bps. default value 800 * 1000 + * @param minVideoBitrate min video bitrate in bps. default value 200 * 1000 + * @throws IllegalArgumentException + */ + public void setVideoBitrate(int initVideoBitrate, int maxVideoBitrate, int minVideoBitrate) { + if (initVideoBitrate <= 0 || maxVideoBitrate <= 0 || minVideoBitrate <= 0) { + throw new IllegalArgumentException("the VideoBitrate must > 0"); + } + + mInitVideoBitrate = initVideoBitrate; + mMaxVideoBitrate = maxVideoBitrate; + mMinVideoBitrate = minVideoBitrate; + mAutoAdjustVideoBitrate = true; + StatsLogReport.getInstance().setAutoAdjustVideoBitrate(true); + } + + /** + * Set video init/min/max bitrate in kbps, and enable video bitrate auto adjustment.
+ * Would take effect on next {@link #startStream()} call. + * + * @param initVideoKBitrate init video bitrate in kbps. + * @param maxVideoKBitrate max video bitrate in kbps. + * @param minVideoKBitrate min video bitrate in kbps. + * @throws IllegalArgumentException + */ + public void setVideoKBitrate(int initVideoKBitrate, + int maxVideoKBitrate, + int minVideoKBitrate) { + setVideoBitrate(initVideoKBitrate * 1024, + maxVideoKBitrate * 1024, + minVideoKBitrate * 1024); + } + + /** + * Set audio sample rate while streaming.
+ * Would take effect on next {@link #startStream()} call. + * default value 44100 + * + * @param sampleRate sample rate in Hz. + * @throws IllegalArgumentException + */ + public void setAudioSampleRate(int sampleRate) { + if (sampleRate <= 0) { + throw new IllegalArgumentException("the AudioSampleRate must > 0"); + } + + mAudioSampleRate = sampleRate; + } + + /** + * Set audio channel number.
+ * Would take effect on next {@link #startStream()} call. + * default value : 1 + * + * @param channels audio channel number, 1 for mono, 2 for stereo. + * @throws IllegalArgumentException + */ + public void setAudioChannels(int channels) { + if (channels != 1 && channels != 2) { + throw new IllegalArgumentException("the AudioChannels must be mono or stereo"); + } + + mAudioChannels = channels; + } + + /** + * Set audio bitrate in bps.
+ * Would take effect on next {@link #startStream()} call. + * default value : 48 * 1000 + * + * @param bitrate audio bitrate in bps. + * @throws IllegalArgumentException + */ + public void setAudioBitrate(int bitrate) { + if (bitrate <= 0) { + throw new IllegalArgumentException("the AudioBitrate must >0"); + } + + mAudioBitrate = bitrate; + } + + /** + * Set audio bitrate in kbps.
+ * Would take effect on next {@link #startStream()} call. + * + * @param kBitrate audio bitrate in kbps. + * @throws IllegalArgumentException + */ + public void setAudioKBitrate(int kBitrate) { + setAudioBitrate(kBitrate * 1024); + } + + /** + * Set enable front camera mirror or not while streaming. + * + * @param enableMirror true to enable, false to disable. + * @deprecated use {@link #setFrontCameraMirror(boolean)} instead. + */ + @Deprecated + public void setEnableCameraMirror(boolean enableMirror) { + setFrontCameraMirror(enableMirror); + } + + /** + * Set enable front camera mirror or not while streaming.
+ * Would take effect immediately while streaming. + * + * @param mirror true to enable, false to disable. + */ + public void setFrontCameraMirror(boolean mirror) { + mFrontCameraMirror = mirror; + updateFrontMirror(); + StatsLogReport.getInstance().setIsFrontCameraMirror(mirror); + } + + /** + * Set initial camera facing.
+ * Set before {@link #startCameraPreview()}, give a chance to set initial camera facing, + * equals {@link #startCameraPreview(int)}.
+ * + * @param facing camera facing. + * @see CameraCapture#FACING_FRONT + * @see CameraCapture#FACING_BACK + */ + public void setCameraFacing(int facing) { + mCameraFacing = facing; + } + + /** + * Start camera preview with default facing, or facing set by + * {@link #setCameraFacing(int)} before. + */ + public void startCameraPreview() { + startCameraPreview(mCameraFacing); + } + + /** + * Start camera preview with given facing. + * + * @param facing camera facing. + * @see CameraCapture#FACING_FRONT + * @see CameraCapture#FACING_BACK + */ + public void startCameraPreview(int facing) { + mCameraFacing = facing; + if (mScreenRenderWidth == 0 || mScreenRenderHeight == 0) { + mDelayedStartCameraPreview = true; + } else { + setPreviewParams(); + mCameraCapture.start(mCameraFacing); + } + } + + /** + * Stop camera preview. + */ + public void stopCameraPreview() { + mCameraCapture.stop(); + } + + private int getShortEdgeLength(int resolution) { + switch (resolution) { + case StreamerConstants.VIDEO_RESOLUTION_360P: + return 360; + case StreamerConstants.VIDEO_RESOLUTION_480P: + return 480; + case StreamerConstants.VIDEO_RESOLUTION_540P: + return 540; + case StreamerConstants.VIDEO_RESOLUTION_720P: + return 720; + default: + return 720; + } + } + + private int align(int val, int align) { + return (val + align - 1) / align * align; + } + + private void calResolution() { + if (mPreviewWidth == 0 && mPreviewHeight == 0) { + int val = getShortEdgeLength(mPreviewResolution); + if (mScreenRenderWidth > mScreenRenderHeight) { + mPreviewHeight = val; + } else { + mPreviewWidth = val; + } + } + if (mTargetWidth == 0 && mTargetHeight == 0) { + int val = getShortEdgeLength(mTargetResolution); + if (mScreenRenderWidth > mScreenRenderHeight) { + mTargetHeight = val; + } else { + mTargetWidth = val; + } + } + + if (mPreviewWidth == 0) { + mPreviewWidth = mPreviewHeight * mScreenRenderWidth / mScreenRenderHeight; + } else if (mPreviewHeight == 0) { + mPreviewHeight = mPreviewWidth * mScreenRenderHeight / mScreenRenderWidth; + } + mPreviewWidth = align(mPreviewWidth, 8); + mPreviewHeight = align(mPreviewHeight, 8); + if (mTargetWidth == 0) { + mTargetWidth = mTargetHeight * mScreenRenderWidth / mScreenRenderHeight; + } else if (mTargetHeight == 0) { + mTargetHeight = mTargetWidth * mScreenRenderHeight / mScreenRenderWidth; + } + mTargetWidth = align(mTargetWidth, 8); + mTargetHeight = align(mTargetHeight, 8); + StatsLogReport.getInstance().setTargetResolution(mTargetWidth, mTargetHeight); + } + + private void updateFrontMirror() { + if (mCameraFacing == CameraCapture.FACING_FRONT) { + mImgTexMixer.setMirror(0, !mFrontCameraMirror); + mVideoEncoderMgt.setImgBufMirror(mFrontCameraMirror); + } else { + mImgTexMixer.setMirror(0, false); + mVideoEncoderMgt.setImgBufMirror(false); + } + } + + private void setAudioParams() { + mAudioResampleFilter.setOutFormat(new AudioBufFormat(AVConst.AV_SAMPLE_FMT_S16, + mAudioSampleRate, mAudioChannels)); + } + + private void setPreviewParams() { + calResolution(); + mWaterMarkCapture.setPreviewSize(mScreenRenderWidth, mScreenRenderHeight); + mWaterMarkCapture.setTargetSize(mTargetWidth, mTargetHeight); + mCameraCapture.setOrientation(mRotateDegrees); + mCameraCapture.setPreviewSize(mPreviewWidth, mPreviewHeight); + mCameraCapture.setPreviewFps(mPreviewFps); + + mImgTexScaleFilter.setTargetSize(mPreviewWidth, mPreviewHeight); + mImgTexMixer.setTargetSize(mTargetWidth, mTargetHeight); + + setAudioParams(); + } + + private void setRecordingParams() { + VideoEncodeFormat videoEncodeFormat = new VideoEncodeFormat(VideoEncodeFormat.MIME_AVC, + mTargetWidth, mTargetHeight, mInitVideoBitrate); + videoEncodeFormat.setFramerate(mTargetFps); + videoEncodeFormat.setIframeinterval(mIFrameInterval); + mVideoEncoderMgt.setEncodeFormat(videoEncodeFormat); + + AudioEncodeFormat audioEncodeFormat = new AudioEncodeFormat(AudioEncodeFormat.MIME_AAC, + AVConst.AV_SAMPLE_FMT_S16, mAudioSampleRate, mAudioChannels, mAudioBitrate); + mAudioEncoderMgt.setEncodeFormat(audioEncodeFormat); + + RtmpPublisher.BwEstConfig bwEstConfig = new RtmpPublisher.BwEstConfig(); + bwEstConfig.initAudioBitrate = mAudioBitrate; + bwEstConfig.initVideoBitrate = mInitVideoBitrate; + bwEstConfig.minVideoBitrate = mMinVideoBitrate; + bwEstConfig.maxVideoBitrate = mMaxVideoBitrate; + mRtmpPublisher.setBwEstConfig(bwEstConfig); + mRtmpPublisher.setFramerate(mTargetFps); + mRtmpPublisher.setVideoBitrate(mMaxVideoBitrate); + mRtmpPublisher.setAudioBitrate(mAudioBitrate); + } + + /** + * Start streaming.
+ * Must be called after {@link #setUrl(String)} and got + * {@link StreamerConstants#KSY_STREAMER_CAMERA_INIT_DONE} event on + * {@link OnInfoListener#onInfo(int, int, int)}. + * + * @return false if it's already streaming, true otherwise. + */ + public boolean startStream() { + if (mIsRecording) { + return false; + } + setAudioParams(); + setRecordingParams(); + mIsRecording = true; + mAudioCapture.start(); + mCameraCapture.startRecord(); + mRtmpPublisher.connect(mUri); + return true; + } + + /** + * Stop streaming. + * + * @return false if it's not streaming, true otherwise. + */ + public boolean stopStream() { + if (!mIsRecording) { + return false; + } + mIsRecording = false; + if (!mIsAudioPreviewing && mAudioCapture.isRecordingState()) { + mAudioCapture.stop(); + } + if (mCameraCapture.isRecording()) { + mCameraCapture.stopRecord(); + } + + mVideoEncoderMgt.getEncoder().stop(); + mAudioEncoderMgt.getEncoder().stop(); + mRtmpPublisher.disconnect(); + return true; + } + + /** + * Get is recording started. + * + * @return true after start, false otherwise. + */ + public boolean isRecording() { + return mIsRecording; + } + + /** + * Set if in audio only streaming mode.
+ * If enable audio only before start stream, then disable it while streaming will + * cause streaming error. Otherwise, start stream with audio only disabled, + * you can enable or disable it dynamically. + * + * @param audioOnly true to enable, false to disable. + */ + public void setAudioOnly(boolean audioOnly) { + if (mIsAudioOnly == audioOnly) { + return; + } + if (audioOnly) { + mVideoEncoderMgt.getSrcPin().disconnect(false); + if (mIsRecording) { + mVideoEncoderMgt.getEncoder().stop(); + } + mRtmpPublisher.setAudioOnly(true); + } else { + mVideoEncoderMgt.getSrcPin().connect(mRtmpPublisher.mVideoSink); + mRtmpPublisher.setAudioOnly(false); + if (mIsRecording) { + mVideoEncoderMgt.getEncoder().start(); + } + } + mIsAudioOnly = audioOnly; + } + + /** + * Should be called on Activity.onResume or Fragment.onResume. + */ + public void onResume() { + mGLRender.onResume(); + } + + /** + * Should be called on Activity.onPause or Fragment.onPause. + */ + public void onPause() { + mGLRender.onPause(); + mCameraCapture.onPause(); + } + + /** + * Set enable debug log or not. + * + * @param enableDebugLog true to enable, false to disable. + */ + public void enableDebugLog(boolean enableDebugLog) { + mEnableDebugLog = enableDebugLog; + StatsLogReport.getInstance().setEnableDebugLog(mEnableDebugLog); + } + + /** + * Get encoded frame number. + * + * @return Encoded frame number on current streaming session. + * @see #getVideoEncoderMgt() + * @see VideoEncoderMgt#getEncoder() + * @see Encoder#getFrameEncoded() + */ + public long getEncodedFrames() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mVideoEncoderMgt.getEncoder().getFrameEncoded(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get dropped frame number. + * + * @return Frame dropped number on current streaming session. + * @see #getVideoEncoderMgt() + * @see VideoEncoderMgt#getEncoder() + * @see Encoder#getFrameDropped() + * @see #getRtmpPublisher() + * @see RtmpPublisher#getDroppedVideoFrames() + */ + public int getDroppedFrameCount() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mVideoEncoderMgt.getEncoder().getFrameDropped() + + mRtmpPublisher.getDroppedVideoFrames(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get dns parse time of current or previous streaming session. + * + * @return dns parse time in ms. + * @see #getRtmpPublisher() + * @see RtmpPublisher#getDnsParseTime() + */ + public int getDnsParseTime() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mRtmpPublisher.getDnsParseTime(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get connect time of current or previous streaming session. + * + * @return connect time in ms. + * @see #getRtmpPublisher() + * @see RtmpPublisher#getConnectTime() + */ + public int getConnectTime() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mRtmpPublisher.getConnectTime(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get current upload speed. + * + * @return upload speed in kbps. + * @see #getCurrentUploadKBitrate() + * @deprecated Use {@link #getCurrentUploadKBitrate()} instead. + */ + @Deprecated + public float getCurrentBitrate() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return getCurrentUploadKBitrate(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get current upload speed. + * + * @return upload speed in kbps. + * @see #getRtmpPublisher() + * @see RtmpPublisher#getCurrentUploadKBitrate() + */ + public int getCurrentUploadKBitrate() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mRtmpPublisher.getCurrentUploadKBitrate(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get total uploaded data of current streaming session. + * + * @return uploaded data size in kbytes. + * @see #getRtmpPublisher() + * @see RtmpPublisher#getUploadedKBytes() + */ + public int getUploadedKBytes() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mRtmpPublisher.getUploadedKBytes(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return 0; + } + } + + /** + * Get host ip of current or previous streaming session. + * + * @return host ip in format as 120.4.32.122 + * @see #getRtmpPublisher() + * @see RtmpPublisher#getHostIp() + */ + public String getRtmpHostIP() { + if (StatsLogReport.getInstance().isPermitLogReport()) { + return mRtmpPublisher.getHostIp(); + } else { + Log.w(TAG, "you must enableStreamStatModule"); + return ""; + } + } + + /** + * Set info listener. + * + * @param listener info listener + */ + public void setOnInfoListener(OnInfoListener listener) { + mOnInfoListener = listener; + } + + /** + * Set error listener. + * + * @param listener error listener + */ + public void setOnErrorListener(OnErrorListener listener) { + mOnErrorListener = listener; + } + + /** + * Switch camera facing between front and back. + */ + public void switchCamera() { + mCameraCapture.switchCamera(); + } + + /** + * Get if current camera in use is front camera.
+ * + * @return true if front camera in use false otherwise. + */ + public boolean isFrontCamera() { + return mCameraFacing == CameraCapture.FACING_FRONT; + } + + /** + * Get if torch supported on current camera facing. + * + * @return true if supported, false if not. + * @see #getCameraCapture() + * @see CameraCapture#isTorchSupported() + */ + public boolean isTorchSupported() { + return mCameraCapture.isTorchSupported(); + } + + /** + * Toggle torch of current camera. + * + * @param open true to turn on, false to turn off. + * @return true if success, false if failed or on invalid mState. + * @see #getCameraCapture() + * @see CameraCapture#toggleTorch(boolean) + */ + public boolean toggleTorch(boolean open) { + return mCameraCapture.toggleTorch(open); + } + + /** + * @deprecated Use {@link #startBgm(String, boolean)} instead. + */ + @Deprecated + public boolean startMixMusic(String path, boolean loop) { + startBgm(path, loop); + return true; + } + + /** + * @deprecated Use {@link #stopBgm()} instead. + */ + @Deprecated + public boolean stopMixMusic() { + stopBgm(); + return true; + } + + /** + * Start bgm play. + * + * @param path bgm path. + * @param loop true if loop this music, false if not. + */ + public void startBgm(String path, boolean loop) { + if (mIsAudioPreviewing) { + mAudioPlayerCapture.setMute(true); + } + mAudioPlayerCapture.start(path, loop); + } + + /** + * Stop bgm play. + */ + public void stopBgm() { + mAudioPlayerCapture.stop(); + } + + /** + * Set if headset plugged. + * + * @deprecated use {@link #setEnableAudioMix(boolean)} instead. + * @param isPlugged true if plugged, false if not. + */ + @Deprecated + public void setHeadsetPlugged(boolean isPlugged) { + setEnableAudioMix(isPlugged); + } + + /** + * Set if enable audio mix, usually set true when headset plugged. + * + * @param enable true to enable, false to disable. + */ + public void setEnableAudioMix(boolean enable) { + mEnableAudioMix = enable; + if (mEnableAudioMix) { + mAudioPlayerCapture.mSrcPin.connect(mAudioMixer.getSinkPin(1)); + } else { + mAudioPlayerCapture.mSrcPin.disconnect(mAudioMixer.getSinkPin(1), false); + } + } + + /** + * Set mic volume. + * + * @param volume volume in 0~1.0f. + */ + public void setVoiceVolume(float volume) { + mAudioMixer.setInputVolume(0, volume); + } + + /** + * @deprecated Use {@link #getImgTexFilterMgt()} and + * {@link ImgTexFilterMgt#setFilter(GLRender, int)} instead. + */ + @Deprecated + public void setBeautyFilter(int beautyFilter) { + mImgTexFilterMgt.setFilter(mGLRender, beautyFilter); + mVideoEncoderMgt.setEnableImgBufBeauty( + beautyFilter != RecorderConstants.FILTER_BEAUTY_DISABLE); + } + + /** + * Set enable cpu beauty filter.
+ * Only need to set when video encode method is + * {@link StreamerConstants#ENCODE_METHOD_SOFTWARE_COMPAT}.
+ * + * @param enable true to enable, false to disable. + * @see #getVideoEncoderMgt() + * @see VideoEncoderMgt#getEncodeMethod() + */ + public void setEnableImgBufBeauty(boolean enable) { + mVideoEncoderMgt.setEnableImgBufBeauty(enable); + } + + /** + * Set if mute audio while streaming. + * + * @param isMute true to mute, false to unmute. + */ + public void setMuteAudio(boolean isMute) { + if (!mIsAudioPreviewing) { + mAudioPlayerCapture.setMute(isMute); + } + mAudioMixer.setMute(isMute); + } + + /** + * Set if start audio preview.
+ * Should start only when headset plugged. + * + * @param enable true to start, false to stop. + */ + public void setEnableAudioPreview(boolean enable) { + mIsAudioPreviewing = enable; + if (enable) { + mAudioCapture.start(); + mAudioPreview.start(); + mAudioPlayerCapture.setMute(true); + } else { + if (!mIsRecording) { + mAudioCapture.stop(); + } + mAudioPreview.stop(); + mAudioPlayerCapture.setMute(false); + } + } + + /** + * @deprecated see {@link #setEnableAudioPreview(boolean)} + */ + @Deprecated + public void setEnableEarMirror(boolean enableEarMirror) { + setEnableAudioPreview(enableEarMirror); + } + + /** + * Get KSYStreamerConfig instance previous set by setConfig. + * + * @return KSYStreamerConfig instance or null. + * @deprecated + */ + @Deprecated + public KSYStreamerConfig getConfig() { + return mConfig; + } + + /** + * @deprecated To implement class extends + * {@link com.ksyun.media.streamer.filter.imgtex.ImgTexFilter} and set it to + * {@link ImgTexFilterMgt}. + */ + @Deprecated + public void setOnPreviewFrameListener(OnPreviewFrameListener listener) { + mCameraCapture.setOnPreviewFrameListener(listener); + } + + /** + * @deprecated To implement class extends + * {@link com.ksyun.media.streamer.filter.audio.AudioFilterBase} and set it to + * {@link com.ksyun.media.streamer.filter.audio.AudioFilterMgt}. + */ + @Deprecated + public void setOnAudioRawDataListener(OnAudioRawDataListener listener) { + mAudioCapture.setOnAudioRawDataListener(listener); + } + + /** + * Set stat info upstreaming log. + * + * @param listener listener + */ + public void setOnLogEventListener(StatsLogReport.OnLogEventListener listener) { + StatsLogReport.getInstance().setOnLogEventListener(listener); + } + + /** + * Set if enable stat info upstreaming. + * + * @param enableStreamStatModule true to enable, false to disable. + */ + public void setEnableStreamStatModule(boolean enableStreamStatModule) { + mEnableStreamStatModule = enableStreamStatModule; + StatsLogReport.getInstance().setIsPermitLogReport(mEnableStreamStatModule); + } + + /** + * Set and show watermark logo both on preview and stream. Support jpeg, png. + * + * @param path logo file path. + * prefix "file://" for absolute path, + * and prefix "assets://" for image resource in assets folder. + * @param x x position for left top of logo relative to the video, between 0~1.0. + * @param y y position for left top of logo relative to the video, between 0~1.0. + * @param w width of logo relative to the video, between 0~1.0, if set to 0, + * width would be calculated by h and logo image radio. + * @param h height of logo relative to the video, between 0~1.0, if set to 0, + * height would be calculated by w and logo image radio. + * @param alpha alpha value,between 0~1.0 + */ + public void showWaterMarkLogo(String path, float x, float y, float w, float h, float alpha) { + alpha = Math.max(0.0f, alpha); + alpha = Math.min(alpha, 1.0f); + mImgTexMixer.setRenderRect(1, x, y, w, h, alpha); + mVideoEncoderMgt.getImgBufMixer().setRenderRect(1, x, y, w, h, alpha); + mWaterMarkCapture.showLogo(mContext, path, w, h); + } + + /** + * Hide watermark logo. + */ + public void hideWaterMarkLogo() { + mWaterMarkCapture.hideLogo(); + } + + /** + * Set and show timestamp both on preview and stream. + * + * @param x x position for left top of timestamp relative to the video, between 0~1.0. + * @param y y position for left top of timestamp relative to the video, between 0~1.0. + * @param w width of timestamp relative to the video, between 0-1.0, + * the height would be calculated automatically. + * @param color color of timestamp, in ARGB. + * @param alpha alpha of timestamp,between 0~1.0. + */ + public void showWaterMarkTime(float x, float y, float w, int color, float alpha) { + alpha = Math.max(0.0f, alpha); + alpha = Math.min(alpha, 1.0f); + mImgTexMixer.setRenderRect(2, x, y, w, 0, alpha); + mVideoEncoderMgt.getImgBufMixer().setRenderRect(2, x, y, w, 0, alpha); + mWaterMarkCapture.showTime(color, "yyyy-MM-dd HH:mm:ss", w, 0); + } + + /** + * Hide timestamp watermark. + */ + public void hideWaterMarkTime() { + mWaterMarkCapture.hideTime(); + } + + /** + * Get current sdk version. + * + * @return version number as 1.0.0.0 + */ + public String getVersion() { + return StatsConstant.SDK_VERSION_SUB_VALUE; + } + + /** + * Release all resources used by KSYStreamer. + */ + public void release() { + mCameraCapture.release(); + mAudioCapture.release(); + mWaterMarkCapture.release(); + mAudioPlayerCapture.release(); + } + + public interface OnInfoListener { + void onInfo(int what, int msg1, int msg2); + } + + public interface OnErrorListener { + void onError(int what, int msg1, int msg2); + } + + private GLRender.GLRenderListener mGLRenderListener = new GLRender.GLRenderListener() { + @Override + public void onReady() { + } + + @Override + public void onSizeChanged(int width, int height) { + mScreenRenderWidth = width; + mScreenRenderHeight = height; + if (mDelayedStartCameraPreview) { + setPreviewParams(); + mCameraCapture.start(mCameraFacing); + mDelayedStartCameraPreview = false; + } + } + + @Override + public void onDrawFrame() { + } + }; +} diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..366dcbd --- /dev/null +++ b/src/README.md @@ -0,0 +1,3 @@ +**注意:** + +该目录下存放了SDK中KSYStreamer类的实现,作为客户使用各个模块类的参考,该文件不参与编译。