庖丁解牛之-Android平台RTSP|RTMP播放器设计

背景

我们在做Android平台RTSP或者RTMP播放器开发的时候,需要注意的点非常多,以下,以大牛直播SDK(官方)的接口为例,大概介绍下相关接口设计:

接口设计

1. Open() 接口

Open接口的目的,主要是创建实例,正常返回player实例句柄,如有多路播放诉求,创建多个实例即可。

	/*** Initialize Player(启动播放实例)** @param ctx: get by this.getApplicationContext()** <pre>This function must be called firstly.</pre>** @return player handle if successful, if return 0, which means init failed. */public native long SmartPlayerOpen(Object ctx);

2. Close()接口

Close接口,和Open()接口对应,负责释放相应实例的资源,调用Close()接口后,记得实例句柄置0即可。

注意:比如一个实例既可以实现播放,又可同时录像,亦或拉流(转发),这种情况下,调Close()接口时,需要确保录像、拉流都正常停止后,再调用。

/*** 关闭播放实例,结束时必须调用close接口释放资源** @param handle: return value from SmartPlayerOpen()** <pre> NOTE: it could not use player handle after call this function. </pre> ** @return {0} if successful*/public native int SmartPlayerClose(long handle);

3. 网络状态回调

一个好的播放器,好的状态回调必不可少,比如网络连通状态、快照、录像状态、当前下载速度等实时反馈,可以让上层开发者更好的掌控播放端状态,给用户更好的播放体验。

	/*** Set callback event(设置事件回调)** @param handle: return value from SmartPlayerOpen()** @param callbackv2: callback function** @return {0} if successful*/public native int SetSmartPlayerEventCallbackV2(long handle, NTSmartEventCallbackV2 callbackv2);

demo实现实例:

    class EventHandeV2 implements NTSmartEventCallbackV2 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1,long param2, String param3, String param4, Object param5) {//Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);String player_event = "";switch (id) {case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:player_event = "开始..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:player_event = "连接中..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:player_event = "连接失败..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:player_event = "连接成功..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:player_event = "连接断开..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:player_event = "停止播放..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:player_event = "分辨率信息: width: " + param1 + ", height: " + param2;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:player_event = "收不到媒体数据,可能是url错误..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:player_event = "切换播放URL..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:player_event = "快照: " + param1 + " 路径:" + param3;if (param1 == 0) {player_event = player_event + ", 截取快照成功";} else {player_event = player_event + ", 截取快照失败";}break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:player_event = "[record]开始一个新的录像文件 : " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:player_event = "[record]已生成一个录像文件 : " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:Log.i(TAG, "Start Buffering");break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:Log.i(TAG, "Buffering:" + param1 + "%");break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:Log.i(TAG, "Stop Buffering");break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:player_event = "download_speed:" + param1 + "Byte/s" + ", "+ (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)+ "KB/s";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE:Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1);player_event = "RTSP error code:" + param1;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NEED_KEY:Log.e(TAG, "RTMP加密流,请设置播放需要的Key..");player_event = "RTMP加密流,请设置播放需要的Key..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_KEY_ERROR:Log.e(TAG, "RTMP加密流,Key错误,请重新设置..");player_event = "RTMP加密流,Key错误,请重新设置..";break;}if (player_event.length() > 0) {Log.i(TAG, player_event);Message message = new Message();message.what = PLAYER_EVENT_MSG;message.obj = player_event;handler.sendMessage(message);}}}

4. 软解码还是硬解码?

随着Android发展越来越好,各个厂商芯片对硬解码的支持,也越来越友好,一般情况下,如果适合通用的产品,在设备性能保障的情况下,优先建议软解,如果特定机型设备,可酌情考虑硬解,硬解码,又分为264硬解、HEVC硬解,直接设置surface模式的硬解,接口如下:

	/*** Set Video H.264 HW decoder(设置H.264硬解码)** @param handle: return value from SmartPlayerOpen()** @param isHWDecoder: 0: software decoder; 1: hardware decoder.** @return {0} if successful*/public native int SetSmartPlayerVideoHWDecoder(long handle, int isHWDecoder);/*** Set Video H.265(hevc) HW decoder(设置H.265硬解码)** @param handle: return value from SmartPlayerOpen()** @param isHevcHWDecoder: 0: software decoder; 1: hardware decoder.** @return {0} if successful*/public native int SetSmartPlayerVideoHevcHWDecoder(long handle, int isHevcHWDecoder);

设置surface模式的硬解:

	/*** 设置视频硬解码下Mediacodec自行绘制模式(此种模式下,硬解码兼容性和效率更好,回调YUV/RGB和快照功能将不可用)** @param handle: return value from SmartPlayerOpen()** @param isHWRenderMode: 0: not enable; 1: 用SmartPlayerSetSurface设置的surface自行绘制** @return {0} if successful*/public native int SmartPlayerSetHWRenderMode(long handle, int isHWRenderMode);/*** 更新硬解码surface** @param handle: return value from SmartPlayerOpen()** @return {0} if successful*/public native int SmartPlayerUpdateHWRenderSurface(long handle);

5. 音频输出类型

音频输出,android平台支持audiotrack模式和opensl es模式,一般来说,考虑到设备通用性,建议采用audiotrack模式。

	/*** Set AudioOutput Type(设置audio输出类型)** @param handle: return value from SmartPlayerOpen()** @param use_audiotrack:** <pre> NOTE: if use_audiotrack with 0: it will use auto-select output devices; if with 1: will use audio-track mode. </pre>** @return {0} if successful*/public native int SmartPlayerSetAudioOutputType(long handle, int use_audiotrack);

6. 缓冲时间设置

缓冲时间,顾名思义,缓存多少数据才开始播放,比如设置2000ms的buffer time,直播模式下,收到2秒数据后,才正常播放。

加大buffer time,会增大播放延迟,好处是,网络抖动的时候,流畅性更好。

	/*** Set buffer(设置缓冲时间,单位:毫秒)** @param handle: return value from SmartPlayerOpen()** @param buffer:** <pre> NOTE: Unit is millisecond, range is 0-5000 ms </pre>** @return {0} if successful*/public native int SmartPlayerSetBuffer(long handle, int buffer);

7. 实时静音、实时音量调节

实时静音、实时音量调节顾名思义,播放端可以实时调整播放音量,或者直接静音掉,特别是多路播放场景下,非常有必要。

此外我们还提供了音量放大的功能,支持放大至原始音量的2倍。

	/*** Set mute or not(设置实时静音)** @param handle: return value from SmartPlayerOpen()** @param is_mute: if with 1:mute, if with 0: does not mute** @return {0} if successful*/public native int SmartPlayerSetMute(long handle, int is_mute);/*** 设置播放音量** @param handle: return value from SmartPlayerOpen()** @param volume: 范围是[0, 100], 0是静音,100是最大音量, 默认是100** @return {0} if successful*/public native int SmartPlayerSetAudioVolume(long handle, int volume);

8. RTSP TCP-UDP模式设置、超时时间设置或模式切换

有的RTSP服务器或摄像机,只支持RTSP TCP模式或者UDP模式,这个时候,默认设置TCP、UDP模式就至关重要,此外,我们还设计支持如TCP或UDP模式收不到数据,在超时时间后,可以自动切换到UDP或TCP。

	/*** 设置RTSP TCP/UDP模式(默认UDP模式)** @param handle: return value from SmartPlayerOpen()** @param is_using_tcp: if with 1, it will via TCP mode, while 0 with UDP mode** @return {0} if successful*/public native int SmartPlayerSetRTSPTcpMode(long handle, int is_using_tcp);/*** 设置RTSP超时时间, timeout单位为秒,必须大于0** @param handle: return value from SmartPlayerOpen()** @param timeout: RTSP timeout setting** @return {0} if successful*/public native int SmartPlayerSetRTSPTimeout(long handle, int timeout);/*** 设置RTSP TCP/UDP自动切换** @param handle: return value from SmartPlayerOpen()** NOTE: 对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式.* 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp.** @param is_auto_switch_tcp_udp 如果设置1的话, sdk将在tcp和udp之间尝试切换播放,如果设置为0,则不尝试切换.** @return {0} if successful*/public native int SmartPlayerSetRTSPAutoSwitchTcpUdp(long handle, int is_auto_switch_tcp_udp);

9. 快速启动

快速启动,主要是针对服务器缓存GOP的场景下,快速刷到最新的数据,确保画面的持续性。

	/*** Set fast startup(设置快速启动模式,此模式针对服务器缓存GOP的场景有效)** @param handle: return value from SmartPlayerOpen()** @param is_fast_startup: if with 1, it will second play back, if with 0: does not it** @return {0} if successful*/public native int SmartPlayerSetFastStartup(long handle, int is_fast_startup);

10. 低延迟模式

低延迟模式下,设置buffer time为0,延迟更低,适用于比如需要操控控制的超低延迟场景下。

	/*** Set low latency mode(设置低延迟模式)** @param handle: return value from SmartPlayerOpen()** @param mode: if with 1, low latency mode, if with 0: normal mode** @return {0} if successful*/public native int SmartPlayerSetLowLatencyMode(long handle, int mode);

11. 视频view旋转、水平|垂直翻转

接口主要用于,比如原始的视频倒置等场景下,设备端无法调整时,通过播放端完成图像的正常角度播放。

	/*** 设置视频垂直反转** @param handle: return value from SmartPlayerOpen()** @param is_flip: 0: 不反转, 1: 反转** @return {0} if successful*/public native int SmartPlayerSetFlipVertical(long handle, int is_flip);/*** 设置视频水平反转** @param handle: return value from SmartPlayerOpen()** @param is_flip: 0: 不反转, 1: 反转** @return {0} if successful*/public native int SmartPlayerSetFlipHorizontal(long handle, int is_flip);/*** 设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能** @param handle: return value from SmartPlayerOpen()** @param degress: 当前支持 0度,90度, 180度, 270度 旋转** @return {0} if successful*/public native int SmartPlayerSetRotation(long handle, int degress);

12. 设置实时回调下载速度

调用实时下载速度接口,通过设置下载速度时间间隔,和是否需要上报当前下载速度,实现APP层和底层SDK更友好的交互。

	/*** Set report download speed(设置实时回调下载速度)** @param handle: return value from SmartPlayerOpen()** @param is_report: if with 1, it will report download speed, it with 0: does not it.** @param report_interval: report interval, unit is second, it must be greater than 0.** @return {0} if successful*/public native int SmartPlayerSetReportDownloadSpeed(long handle, int is_report, int report_interval );

13. 实时快照

简单来说,播放过程中,是不是要存取当前的播放画面。

	/*** Set if needs to save image during playback stream(是否启动快照功能)** @param handle: return value from SmartPlayerOpen()** @param is_save_image: if with 1, it will save current image via the interface of SmartPlayerSaveCurImage(), if with 0: does not it** @return {0} if successful*/public native int SmartPlayerSaveImageFlag(long handle, int is_save_image);/*** Save current image during playback stream(实时快照)** @param handle: return value from SmartPlayerOpen()** @param imageName: image name, which including fully path, "/sdcard/daniuliveimage/daniu.png", etc.** @return {0} if successful*/public native int SmartPlayerSaveCurImage(long handle, String imageName);

14. 扩展录像操作

播放端录像,我们做的非常细化,比如可以只录制音频或者只录制视频,设置录像存储路径,设置单个文件size,如果非AAC数据,可以转AAC后再录像。

	/*** Create file directory(创建录像目录)** @param path,  E.g: /sdcard/daniulive/rec** <pre> The interface is only used for recording the stream data to local side. </pre>** @return {0} if successful*/public native int SmartPlayerCreateFileDirectory(String path);/*** Set recorder directory(设置录像目录)** @param handle: return value from SmartPlayerOpen()** @param path: the directory of recorder file** <pre> NOTE: make sure the path should be existed, or else the setting failed. </pre>** @return {0} if successful*/public native int SmartPlayerSetRecorderDirectory(long handle, String path);/*** Set the size of every recorded file(设置单个录像文件大小,如超过设定大小则自动切换到下个文件录制)** @param handle: return value from SmartPlayerOpen()** @param size: (MB), (5M~500M), if not in this range, set default size with 200MB.** @return {0} if successful*/public native int SmartPlayerSetRecorderFileMaxSize(long handle, int size);/** 设置录像时音频转AAC编码的开关** @param handle: return value from SmartPlayerOpen()** aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.** @param is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac,如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.** 注意: 转码会增加性能消耗** @return {0} if successful*/public native int SmartPlayerSetRecorderAudioTranscodeAAC(long handle, int is_transcode);/**设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关**@param is_record_video: 1 表示录制视频, 0 表示不录制视频, 默认是1** @return {0} if successful*/public native int SmartPlayerSetRecorderVideo(long handle, int is_record_video);/**设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关**@param is_record_audio: 1 表示录制音频, 0 表示不录制音频, 默认是1** @return {0} if successful*/public native int SmartPlayerSetRecorderAudio(long handle, int is_record_audio);
	/*** Start recorder stream(开始录像)** @param handle: return value from SmartPlayerOpen()** @return {0} if successful*/public native int SmartPlayerStartRecorder(long handle);/*** Stop recorder stream(停止录像)** @param handle: return value from SmartPlayerOpen()** @return {0} if successful*/public native int SmartPlayerStopRecorder(long handle);

15. 拉流回调编码后的数据(配合转发模块使用)

拉流回调编码后的数据,主要是为了配合转发模块使用,比如拉取rtsp流数据,直接转RTMP推送到RTMP服务。

	/** 设置拉流时音频转AAC编码的开关** @param handle: return value from SmartPlayerOpen()** aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.** @param is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.* 注意: 转码会增加性能消耗** @return {0} if successful*/public native int SmartPlayerSetPullStreamAudioTranscodeAAC(long handle, int is_transcode);/*** Start pull stream(开始拉流,用于数据转发,只拉流不播放)** @param handle: return value from SmartPlayerOpen()** @return {0} if successful*/public native int SmartPlayerStartPullStream(long handle);/*** Stop pull stream(停止拉流)** @param handle: return value from SmartPlayerOpen()** @return {0} if successful*/public native int SmartPlayerStopPullStream(long handle);
	/*** Set Audio Data Callback(设置回调编码后音频数据)** @param handle: return value from SmartPlayerOpen()** @param audio_data_callback: Audio Data Callback.** @return {0} if successful*/public native int SmartPlayerSetAudioDataCallback(long handle, Object audio_data_callback);/*** Set Video Data Callback(设置回调编码后视频数据)** @param handle: return value from SmartPlayerOpen()** @param video_data_callback: Video Data Callback.** @return {0} if successful*/public native int SmartPlayerSetVideoDataCallback(long handle, Object video_data_callback);

16. H264用户数据回调或SEI数据回调

如发送端在264编码时,加了自定义的user data数据,可以通过以下接口实现数据回调,如需直接回调SEI数据,调下面SEI回调接口即可。

	/*** Set user data Callback(设置回调SEI扩展用户数据)** @param handle: return value from SmartPlayerOpen()** @param user_data_callback: user data callback.** @return {0} if successful*/public native int SmartPlayerSetUserDataCallback(long handle, Object user_data_callback);/*** Set SEI data Callback(设置回调SEI数据)** @param handle: return value from SmartPlayerOpen()** @param sei_data_callback: sei data callback.** @return {0} if successful*/public native int SmartPlayerSetSEIDataCallback(long handle, Object sei_data_callback);

17. 设置回调解码后YUV、RGB数据

如需对解码后的yuv或rgb数据,进行二次处理,如人脸识别等,可以通回调yuv rgb接口实现数据二次处理。

以YUV为例:

    class I420ExternalRender implements NTExternalRender {// public static final int NT_FRAME_FORMAT_RGBA = 1;// public static final int NT_FRAME_FORMAT_ABGR = 2;// public static final int NT_FRAME_FORMAT_I420 = 3;private int width_ = 0;private int height_ = 0;private int y_row_bytes_ = 0;private int u_row_bytes_ = 0;private int v_row_bytes_ = 0;private ByteBuffer y_buffer_ = null;private ByteBuffer u_buffer_ = null;private ByteBuffer v_buffer_ = null;@Overridepublic int getNTFrameFormat() {Log.i(TAG, "I420ExternalRender::getNTFrameFormat return "+ NT_FRAME_FORMAT_I420);return NT_FRAME_FORMAT_I420;}@Overridepublic void onNTFrameSizeChanged(int width, int height) {width_ = width;height_ = height;y_row_bytes_ = (width_ + 15) & (~15);u_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);v_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_ * height_);u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_* ((height_ + 1) / 2));v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_* ((height_ + 1) / 2));Log.i(TAG, "I420ExternalRender::onNTFrameSizeChanged width_="+ width_ + " height_=" + height_ + " y_row_bytes_="+ y_row_bytes_ + " u_row_bytes_=" + u_row_bytes_+ " v_row_bytes_=" + v_row_bytes_);}@Overridepublic ByteBuffer getNTPlaneByteBuffer(int index) {if (index == 0) {return y_buffer_;} else if (index == 1) {return u_buffer_;} else if (index == 2) {return v_buffer_;} else {Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);return null;}}@Overridepublic int getNTPlanePerRowBytes(int index) {if (index == 0) {return y_row_bytes_;} else if (index == 1) {return u_row_bytes_;} else if (index == 2) {return v_row_bytes_;} else {Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);return 0;}}public void onNTRenderFrame(int width, int height, long timestamp) {if (y_buffer_ == null)return;if (u_buffer_ == null)return;if (v_buffer_ == null)return;y_buffer_.rewind();u_buffer_.rewind();v_buffer_.rewind();/*if ( !is_saved_image ){is_saved_image = true;int y_len = y_row_bytes_*height_;int u_len = u_row_bytes_*((height_+1)/2);int v_len = v_row_bytes_*((height_+1)/2);int data_len = y_len + (y_row_bytes_*((height_+1)/2));byte[] nv21_data = new byte[data_len];byte[] u_data = new byte[u_len];byte[] v_data = new byte[v_len];y_buffer_.get(nv21_data, 0, y_len);u_buffer_.get(u_data, 0, u_len);v_buffer_.get(v_data, 0, v_len);int[] strides = new int[2];strides[0] = y_row_bytes_;strides[1] = y_row_bytes_;int loop_row_c = ((height_+1)/2);int loop_c = ((width_+1)/2);int dst_row = y_len;int src_v_row = 0;int src_u_row = 0;for ( int i = 0; i < loop_row_c; ++i){int dst_pos = dst_row;for ( int j = 0; j <loop_c; ++j ){nv21_data[dst_pos++] = v_data[src_v_row + j];nv21_data[dst_pos++] = u_data[src_u_row + j];}dst_row   += y_row_bytes_;src_v_row += v_row_bytes_;src_u_row += u_row_bytes_;}String imagePath = "/sdcard" + "/" + "testonv21" + ".jpeg";Log.e(TAG, "I420ExternalRender::begin test save iamge++ image_path:" + imagePath);try{File file = new File(imagePath);FileOutputStream image_os = new FileOutputStream(file);YuvImage image = new YuvImage(nv21_data, ImageFormat.NV21, width_, height_, strides);image.compressToJpeg(new android.graphics.Rect(0, 0, width_, height_), 50, image_os);image_os.flush();image_os.close();}catch(IOException e){e.printStackTrace();}Log.e(TAG, "I420ExternalRender::begin test save iamge--");}*/Log.i(TAG, "I420ExternalRender::onNTRenderFrame w=" + width + " h=" + height + " timestamp=" + timestamp);// copy buffer// test// byte[] test_buffer = new byte[16];// y_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame y data:" + bytesToHexString(test_buffer));// u_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame u data:" + bytesToHexString(test_buffer));// v_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame v data:" + bytesToHexString(test_buffer));}}

18. 设置视频画面填充模式

设置视频画面的填充模式,如填充整个view、等比例填充view,如不设置,默认填充整个view。

相关接口设计如下:

	/*** 设置视频画面的填充模式,如填充整个view、等比例填充view,如不设置,默认填充整个view* @param handle: return value from SmartPlayerOpen()* @param render_scale_mode 0: 填充整个view; 1: 等比例填充view, 默认值是0* @return {0} if successful*/public native int SmartPlayerSetRenderScaleMode(long handle, int render_scale_mode);

19. 设置SurfaceView模式下render类型

format: 0: RGB565格式,如不设置,默认此模式; 1: ARGB8888格式

	/*** 设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),render类型** @param handle: return value from SmartPlayerOpen()** @param format: 0: RGB565格式,如不设置,默认此模式; 1: ARGB8888格式** @return {0} if successful*/public native int SmartPlayerSetSurfaceRenderFormat(long handle, int format);

20. 设置surfaceview模式下抗锯齿

需要注意的是:抗锯齿模式开启后,会增加性能消耗。

	/*** 设置SurfaceView模式下(NTRenderer.CreateRenderer第二个参数传false的情况),抗锯齿效果,注意:抗锯齿模式开启后,可能会影像性能,请慎用** @param handle: return value from SmartPlayerOpen()** @param isEnableAntiAlias: 0: 如不设置,默认不开启抗锯齿模式; 1: 开启抗锯齿模式** @return {0} if successful*/public native int SmartPlayerSetSurfaceAntiAlias(long handle, int isEnableAntiAlias);

总结

以上就是Android平台RTSP、RTMP播放器接口设计需要参考的点,对于大多数开发者来说,不一定需要实现上述所有部分,只要按照产品诉求,实现其中的40%就足够满足特定场景使用了。

一个好的播放器,特别是要满足低延迟稳定的播放(毫秒级延迟),需要注意的点远不止如此,感兴趣的开发者,可以参考blog其他文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/553031.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Android端实现轻量级RTSP服务(类似于IPC)

为什么要设计轻量级RTSP服务 首先声明一点&#xff0c;本blog提到的轻量级RTSP服务&#xff0c;类似于网络摄像头&#xff08;IPC&#xff09;&#xff0c;而非传统意义的接受外部推流的RTSP服务器。 轻量级RTSP服务解决的核心痛点&#xff1a;避免用户单独部署RTSP或者RTMP服…

Android平台基于RTMP或RTSP的一对一音视频互动技术方案探讨

背景 随着智能门禁等物联网产品的普及&#xff0c;越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC&#xff0c;优点不再赘述&#xff0c;我们这里先说说可能需要面临的问题&#xff1a;WebRTC的服务器部署非常复杂&#xff0c;可…

微信公众号怎么发送模板消息 微信公众平台模板消息免费发送的技巧

想要发免费的模板消息&#xff0c;该怎么发布?下面我们就来看看详细的教程。 1、首先我们需要在微信公众号里面开通模板消息功能&#xff0c;没有开通的需要去申请&#xff0c;不然就用不了&#xff0c;如下图所示。 微信公众号怎么发送模板消息?微信公众平台模板消息免费发…

如何理解面向过程和面向对象?

一句话理解面向对象 有人说&#xff1a;“如果上帝是程序员&#xff0c;他怎么创造世界上的所有动物。”&#xff0c;理解这个问题就理解了面向对像。 面向过程和面向对象区别&#xff1f; 面向过程的思路&#xff1a;什么事都自己做&#xff1b;分析解决问题所需的步骤&…

如何实现Android平台GB28181前端设备接入

技术背景 在实现Android平台GB28181前端设备接入之前&#xff0c;我们几年前就有了非常成熟的RTMP推送、RTSP推送和轻量级RTSP服务等模块&#xff0c;特别是RTMP推送&#xff0c;行业内应用非常广泛&#xff0c;好多开发者可能会问&#xff0c;既然有了以上模块&#xff0c;干…

foxmail怎么加入黑名单 foxmail导入黑名单邮箱地址的教程

1、首先&#xff0c;先进入到了foxmail的窗口的界面当中&#xff0c;进行点击菜单中工具&#xff0c;弹出了下拉菜单中&#xff0c;进行选中为“系统工具” foxmail怎么加入黑名单? foxmail导入黑名单邮箱地址的教程 2、进入到了的系统的设置的界面中&#xff0c;进行选中反…

Android前端音视频数据接入GB28181平台意义

技术背景 在我们研发Android平台GB28181前端音视频接入模块之前&#xff0c;业内听到最多的是&#xff0c;如何用Android或者Windows端&#xff0c;在没有国标IPC设备的前提下&#xff0c;模拟GB28181的信令和媒体流交互流程&#xff0c;实现GB28181整体方案的测试&#xff1f…

QQ浏览器怎样在首页显示优先推荐的网站

QQ浏览器怎样在首页显示优先推荐的网站&#xff1f;QQ浏览器显示优先推荐的网站的方法 1&#xff0c;在手机桌面上找到QQ浏览器的图标&#xff0c;点击打开。 QQ浏览器怎样在首页显示优先推荐的网站&#xff1f;QQ浏览器显示优先推荐的网站的方法[多图] 2&#xff0c;点击下…

push_back还是emplace_back?

背景和区别 emplace_back() 是 C11 之后&#xff0c;vector容器中添加的新方法&#xff0c;和 push_back()一样&#xff0c;都是在容器末尾添加一个新的元素&#xff0c;相对于push_back函数&#xff0c;它减少了一次类的构造。不同的是emplace_back() 在效率上相比较于 push_…

std::tuple还是struct?

std::tuple是C11提供的新模板类&#xff0c;可以翻译为“元组”&#xff0c;可把多个不同类型的变量组合成一个对象。std::tuple可看做std::pair的泛化实现&#xff0c;std::pair包含两个元素&#xff0c;std::tuple 可以同时包含多个元素&#xff0c;它拥有 struct 的表现&…

微软发动图明示新一代操作系统Windows 11

可能感到关注度不够&#xff0c;微软再次出面预热所谓的新一代Windows操作系统&#xff0c;阿拉伯数字“11”图形赫然出现在预热动图中&#xff0c;可以说暗示得不要再明显了。 值得注意的是&#xff0c;动图中11的两个1中间还有一位正打坐的小人&#xff0c;配合微软关注大脑…

std::atomic和std::mutex区别

​std::atomic介绍​ ​模板类std::atomic是C11提供的原子操作类型&#xff0c;头文件 #include<atomic>。​在多线程调用下&#xff0c;利用std::atomic可实现数据结构的无锁设计。​​ ​和互斥量的不同之处在于&#xff0c;std::atomic原子操作&#xff0c;主要是保…

C++ std::remove/std::remove_if/erase用法探讨

​std::remove 不会改变输入vector/string的长度。其过程相当于去除指定的字符&#xff0c;剩余字符往前靠。后面的和原始字符保持一致。​ 需要注意的是&#xff0c;remove函数是通过覆盖移去的&#xff0c;如果容器最后一个值刚好是需要删除的&#xff0c;则它无法覆盖掉容器…

深度技术Win11 64位最新旗舰版镜像V2021.08

深度技术Win11 64位最新旗舰版镜像V2021.08是微软最新版本的电脑操作系统&#xff0c;系统稳定性的进一步优化和提升&#xff0c;可以更好的获得整个纯版本系统的稳定性。支持系统智能激活服务&#xff0c;用户可以快速激活系统&#xff0c;提高系统使用率&#xff0c;有需要的…

再谈NULL和nullptr(C++11)区别

在谈NULL和nullptr区别之前&#xff0c;我们先看段代码&#xff1a; #include "stdafx.h" #include <iostream>using namespace std; void func(void *p) {cout << "p is pointer " << p << endl; } void func(int num) {cout &l…

C/C++如何快速区分指针数组|数组指针|函数指针|指针函数

如何区分这些概念&#xff0c;主要还是看后面两个字&#xff0c;中文表达模式“​表语定性名词​”&#xff0c;​所以关键的都是后面的这个名词​&#xff1a; ​指针数组​&#xff1a;一个数组&#xff0c;数组元素是指针&#xff0c;如&#xff1a; int* p[20]; ​数组指…

Foxmail记事插入的表格怎么设置单元格边距

Foxmail在使用记事时候&#xff0c;插入了表格&#xff0c;该怎么设置单元格的边距呢?下面我们就来看看详细的教程。 Foxmail记事插入的表格怎么设置单元格边距? 1、下载并安装Foxmail邮件客户端。 Foxmail记事插入的表格怎么设置单元格边距? 2、打开Foxmail邮件客户端&…

C++11新特性探索:原始字符串字面值(raw string literal)

原始字符串字面值(raw string literal)是C11引入的新特性。 原始字符串简单来说&#xff0c;“原生的、不加处理的”&#xff0c;字符表示的就是自己&#xff08;所见即所得&#xff09;&#xff0c;引号、斜杠无需 “\” 转义&#xff0c;比如常用的目录表示&#xff0c;引入…

C++11新特性探究:显式override和final

C中&#xff0c;我们一般可以以基类声明纯虚函数&#xff0c;然后让派生类继承并重写这个虚函数&#xff0c;用​override表示显示覆盖基类方法&#xff0c;但一直没有提供一种方法来阻止派生类继承基类的虚函数。 C11标准引入了final说明符&#xff0c;很好的解决了上面的问题…

微信公众号小程序与服务号和订阅号有什么区别

微信中有小程序&#xff0c;有微信订阅号、微信服务号&#xff0c;这些功能之间有什么区别?下面我们就来详细介绍一下。 一、适合的场景 都是搭建在微信平台&#xff0c;功能、主要用途有些区别&#xff0c;使用不同的场景 微信公众号小程序与服务号和订阅号有什么区别? …