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

背景

随着智能门禁等物联网产品的普及,越来越多的开发者对音视频互动体验提出了更高的要求。目前市面上大多一对一互动都是基于WebRTC,优点不再赘述,我们这里先说说可能需要面临的问题:WebRTC的服务器部署非常复杂,可以私有部署,但是非常复杂。传输基于UDP,很难保证传输质量,由于UDP是不可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,难以应对复杂的互联网环境,如跨区跨运营商、低带宽、高丢包等场景,行话说的好:从demo到实用,中间还差1万个WebRTC。

其他技术方案

  1. 内网环境下的RTSP轻量级服务;
  2. 基于RTMP的公网或内网技术方案。

本方案系基于现有RTMP或内置RTSP服务、RTMP/RTSP直播播放模块,产品稳定度高,在保证超低延迟的基础上,加入噪音抑制、回音消除、自动增益控制等特性,确保通话体验(如需更好的消除效果,亦可考虑如麦克风阵列等技术方案),采用通用的RTMP服务器(如nginx、SRS)或自身的轻量级RTSP服务,更有利于私有部署,便于支持H.264的扩展SEI消息发送机制,方便扩展特定机型H.265编码支持。

技术实现

废话不多说,先上图:

关键demo代码说明:

拉流播放:

        btnPlaybackStartStopPlayback.setOnClickListener(new Button.OnClickListener() {  //  @Override  public void onClick(View v) {  if(isPlaybackViewStarted){Log.i(PLAY_TAG, "Stop playback stream++");btnPlaybackStartStopPlayback.setText("开始播放 ");//btnPopInputText.setEnabled(true);btnPlaybackPopInputUrl.setEnabled(true);btnPlaybackHardwareDecoder.setEnabled(true);btnPlaybackSetPlayBuffer.setEnabled(true);btnPlaybackFastStartup.setEnabled(true);if ( playerHandle != 0 ){libPlayer.SmartPlayerStopPlay(playerHandle);libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}isPlaybackViewStarted = false;Log.i(PLAY_TAG, "Stop playback stream--");}else{Log.i(PLAY_TAG, "Start playback stream++");playerHandle = libPlayer.SmartPlayerOpen(curContext);if(playerHandle == 0){Log.e(PLAY_TAG, "surfaceHandle with nil..");return;}libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,new EventHandePlayerV2());libPlayer.SmartPlayerSetSurface(playerHandle, playerSurfaceView); 	//if set the second param with null, it means it will playback audio only..// libPlayer.SmartPlayerSetSurface(playerHandle, null); libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);// External Render test//libPlayer.SmartPlayerSetExternalRender(playerHandle, new RGBAExternalRender());//libPlayer.SmartPlayerSetExternalRender(playerHandle, new I420ExternalRender());libPlayer.SmartPlayerSetExternalAudioOutput(playerHandle, new PlayerExternalPcmOutput());libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);libPlayer.SmartPlayerSetBuffer(playerHandle, playbackBuffer);libPlayer.SmartPlayerSetFastStartup(playerHandle, isPlaybackFastStartup?1:0);if ( isPlaybackMute ){libPlayer.SmartPlayerSetMute(playerHandle, isPlaybackMute?1:0);}if (isPlaybackHardwareDecoder) {int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle,1);int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(playerHandle,1);Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);}if( playbackUrl == null ){Log.e(PLAY_TAG, "playback URL with NULL..."); return;}libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);int iPlaybackRet = libPlayer.SmartPlayerStartPlay(playerHandle);if( iPlaybackRet != 0 ){libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;Log.e(PLAY_TAG, "StartPlayback strem failed.."); return;}btnPlaybackStartStopPlayback.setText("停止播放 ");btnPlaybackPopInputUrl.setEnabled(false);btnPlaybackHardwareDecoder.setEnabled(false);btnPlaybackSetPlayBuffer.setEnabled(false);btnPlaybackFastStartup.setEnabled(false);isPlaybackViewStarted = true;Log.i(PLAY_TAG, "Start playback stream--");}}});

拉流端实时音量调节:

		audioVolumeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int i, boolean b) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//Log.i(TAG, "开始拖动");}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {Log.i(TAG, "停止拖动, CurProgress: " + seekBar.getProgress());curAudioVolume = seekBar.getProgress();audioVolumeText.setText("当前音量: " + curAudioVolume);if(playerHandle != 0){libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);}}});}

回调后的PCM数据,传给推送端,用于音频处理

	class NTAudioRecordV2CallbackImpl implements NTAudioRecordV2Callback{@Overridepublic void onNTAudioRecordV2Frame(ByteBuffer data, int size, int sampleRate, int channel, int per_channel_sample_number){/*Log.i(TAG, "onNTAudioRecordV2Frame size=" + size + " sampleRate=" + sampleRate + " channel=" + channel+ " per_channel_sample_number=" + per_channel_sample_number);*/if ( (isPushingRtmp || isRTSPPublisherRunning) && publisherHandle != 0 ){libPublisher.SmartPublisherOnPCMData(publisherHandle, data, size, sampleRate, channel, per_channel_sample_number);}}}
    class PlayerExternalPcmOutput implements NTExternalAudioOutput{    	private int sample_rate_ = 0;private int channel_ = 0;private int sample_size = 0;private int buffer_size = 0;private ByteBuffer pcm_buffer_ = null;@Overridepublic ByteBuffer getPcmByteBuffer(int size){//Log.i("getPcmByteBuffer", "size: " + size);if(size < 1){return null;}if(buffer_size != size){buffer_size = size;pcm_buffer_ = ByteBuffer.allocateDirect(buffer_size);}return pcm_buffer_;}public void onGetPcmFrame(int ret, int sampleRate, int channel, int sampleSize, int is_low_latency){/*Log.i("onGetPcmFrame", "ret: " + ret + ", sampleRate: " + sampleRate + ", channel: " + channel + ", sampleSize: " + sampleSize +",is_low_latency:" + is_low_latency + " buffer_size:" + buffer_size);*/if ( pcm_buffer_ == null)return;pcm_buffer_.rewind();if ( ret == 0 && (isPushingRtmp || isRTSPPublisherRunning)){libPublisher.SmartPublisherOnFarEndPCMData(publisherHandle, pcm_buffer_, sampleRate, channel, sampleSize, is_low_latency);if (is_audio_mix_){libPublisher.SmartPublisherOnMixPCMData(publisherHandle, 1, pcm_buffer_, 0, buffer_size, sampleRate, channel, sampleSize);}/*java.nio.ByteOrder old_order = pcm_buffer_.order();pcm_buffer_.order(java.nio.ByteOrder.nativeOrder());java.nio.ShortBuffer short_buffer = pcm_buffer_.asShortBuffer();pcm_buffer_.order(old_order);short[] short_array =  new short[short_buffer.remaining()];short_buffer.get(short_array);libPublisher.SmartPublisherOnMixPCMShortArray(publisherHandle, 1, short_array, 0, short_array.length, sampleRate, channel, sampleSize);*/}// test/*byte[] test_buffer = new byte[16];pcm_buffer_.get(test_buffer);Log.i(TAG, "onGetPcmFrame data:" + bytesToHexString(test_buffer));*/}}

推送端:

RTMP推送:

    class ButtonPushStartListener implements OnClickListener{public void onClick(View v){    if (isPushingRtmp){stopPush();if (!isRTSPPublisherRunning) {ConfigControlEnable(true);}btnPushStartStop.setText("推送RTMP");isPushingRtmp = false;return;}Log.i(PUSH_TAG, "onClick start push rtmp..");if (libPublisher == null)return;if (!isRTSPPublisherRunning) {InitPusherAndSetConfig();}if ( inputPushURL != null && inputPushURL.length() > 1 ){publishURL = inputPushURL;Log.i(PUSH_TAG, "start, input publish url:" + publishURL);}else{publishURL = basePushURL + String.valueOf((int)( System.currentTimeMillis() % 1000000));Log.i(PUSH_TAG, "start, generate random url:" + publishURL);}printPushText = "URL:" + publishURL;Log.i(PUSH_TAG, printPushText);textPushCurURL = (TextView)findViewById(R.id.txt_push_cur_url);textPushCurURL.setText(printPushText);Log.i(PUSH_TAG, "videoWidth: "+ pushVideoWidth + " videoHeight: " + pushVideoHeight + " pushType:" + pushType);if ( libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0 ){Log.e(PUSH_TAG, "Failed to set rtmp pusher URL..");}int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);if (startRet != 0) {isPushingRtmp = false;Log.e(TAG, "Failed to start push stream..");return;}if ( !isRTSPPublisherRunning ) {if (pushType == 0 || pushType == 1) {CheckInitAudioRecorder();    //enable pure video publisher..}ConfigControlEnable(false);}btnPushStartStop.setText("停止推送 ");isPushingRtmp = true;}};

轻量级RTSP服务模式:

	//启动/停止RTSP服务class ButtonRtspServiceListener implements OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("启动RTSP服务");btnRtspPublisher.setEnabled(false);isRTSPServiceRunning = false;return;}Log.i(TAG, "onClick start rtsp service..");rtsp_handle_ = libPublisher.OpenRtspServer(0);if (rtsp_handle_ == 0) {Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");} else {int port = 8554;if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");}//String user_name = "admin";//String password = "12345";//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {Log.i(TAG, "启动rtsp server 成功!");} else {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");}btnRtspService.setText("停止RTSP服务");btnRtspPublisher.setEnabled(true);isRTSPServiceRunning = true;}}}//发布/停止RTSP流class ButtonRtspPublisherListener implements OnClickListener {public void onClick(View v) {if (isRTSPPublisherRunning) {stopRtspPublisher();if (!isPushingRtmp) {ConfigControlEnable(true);}btnRtspPublisher.setText("发布RTSP流");btnGetRtspSessionNumbers.setEnabled(false);btnRtspService.setEnabled(true);isRTSPPublisherRunning = false;return;}Log.i(TAG, "onClick start rtsp publisher..");if (!isPushingRtmp) {InitPusherAndSetConfig();}if (publisherHandle == 0) {Log.e(TAG, "Start rtsp publisher, publisherHandle is null..");return;}String rtsp_stream_name = "stream1";libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);libPublisher.ClearRtspStreamServer(publisherHandle);libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);if (libPublisher.StartRtspStream(publisherHandle, 0) != 0) {Log.e(TAG, "调用发布rtsp流接口失败!");return;}if (!isPushingRtmp) {if (pushType == 0 || pushType == 1) {CheckInitAudioRecorder();    //enable pure video publisher..}ConfigControlEnable(false);}btnRtspPublisher.setText("停止RTSP流");btnGetRtspSessionNumbers.setEnabled(true);btnRtspService.setEnabled(false);isRTSPPublisherRunning = true;}};

RTMP推送和轻量级RTSP服务,可以在一个实例里面处理,所以推送参数的初始化,只需要调用一次即可。

	private void InitPusherAndSetConfig() {Log.i(TAG, "videoWidth: " + pushVideoWidth + " videoHeight: " + pushVideoHeight+ " pushType:" + pushType);int audio_opt = 1;int video_opt = 1;if ( pushType == 1 ){video_opt = 0;}else if (pushType == 2 ){audio_opt = 0;}publisherHandle = libPublisher.SmartPublisherOpen(curContext, audio_opt, video_opt,pushVideoWidth, pushVideoHeight);if ( publisherHandle == 0  ){return;}if(videoEncodeType == 1){int h264HWKbps = setHardwareEncoderKbps(true, pushVideoWidth,pushVideoHeight);Log.i(TAG, "h264HWKbps: " + h264HWKbps);int isSupportH264HWEncoder = libPublisher.SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);if (isSupportH264HWEncoder == 0) {Log.i(TAG, "Great, it supports h.264 hardware encoder!");}}else if (videoEncodeType == 2){int hevcHWKbps = setHardwareEncoderKbps(false, pushVideoWidth,pushVideoHeight);Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);int isSupportHevcHWEncoder = libPublisher.SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);if (isSupportHevcHWEncoder == 0) {Log.i(TAG, "Great, it supports hevc hardware encoder!");}}if(is_sw_vbr_mode){int is_enable_vbr = 1;int video_quality = CalVideoQuality(pushVideoWidth,pushVideoHeight, true);int vbr_max_bitrate = CalVbrMaxKBitRate(pushVideoWidth,pushVideoHeight);libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);}libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandePublisherV2());//如果想和时间显示在同一行,请去掉'\n'String watermarkText = "大牛直播(daniulive)\n\n";String path = pushLogoPath;if( pushWatemarkType == 0 ){if ( isPushWritelogoFileSuccess )libPublisher.SmartPublisherSetPictureWatermark(publisherHandle, path, WATERMARK.WATERMARK_POSITION_TOPRIGHT, 160, 160, 10, 10);}else if( pushWatemarkType == 1 ){if ( isPushWritelogoFileSuccess )libPublisher.SmartPublisherSetPictureWatermark(publisherHandle, path, WATERMARK.WATERMARK_POSITION_TOPRIGHT, 160, 160, 10, 10);libPublisher.SmartPublisherSetTextWatermark(publisherHandle, watermarkText, 1, WATERMARK.WATERMARK_FONTSIZE_BIG, WATERMARK.WATERMARK_POSITION_BOTTOMRIGHT, 10, 10);//libPublisher.SmartPublisherSetTextWatermarkFontFileName("/system/fonts/DroidSansFallback.ttf");//libPublisher.SmartPublisherSetTextWatermarkFontFileName("/sdcard/DroidSansFallback.ttf");}else if(pushWatemarkType == 2){libPublisher.SmartPublisherSetTextWatermark(publisherHandle, watermarkText, 1, WATERMARK.WATERMARK_FONTSIZE_BIG, WATERMARK.WATERMARK_POSITION_BOTTOMRIGHT, 10, 10);//libPublisher.SmartPublisherSetTextWatermarkFontFileName("/system/fonts/DroidSansFallback.ttf");}else{Log.i(TAG, "no watermark settings..");}//endif ( !is_push_speex ){// set AAC encoderlibPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);}else{// set Speex encoderlibPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);}libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_push_noise_suppression?1:0);libPublisher.SmartPublisherSetAGC(publisherHandle, is_push_agc?1:0);libPublisher.SmartPublisherSetEchoCancellation(publisherHandle, 1, echoCancelDelay);libPublisher.SmartPublisherSetAudioMix(publisherHandle, is_audio_mix_?1:0);libPublisher.SmartPublisherSetInputAudioVolume(publisherHandle, 0 , mic_audio_volume_);if ( is_audio_mix_ ){libPublisher.SmartPublisherSetInputAudioVolume(publisherHandle, 1 , mix_audio_volume_);}libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, push_sw_video_encoder_profile);//libPublisher.SetRtmpPublishingType(0);//libPublisher.SmartPublisherSetGopInterval(publisherHandle, 18*3);//libPublisher.SmartPublisherSetFPS(publisherHandle, 18);libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);//libPublisher.SmartPublisherSetSWVideoBitRate(600, 1200);}

相关封装:

	//停止rtmp推送private void stopPush() {if(!isPushingRtmp){return;}if ( !isRTSPPublisherRunning) {if (audioRecord_ != null) {Log.i(TAG, "stopPush, call audioRecord_.StopRecording..");audioRecord_.Stop();if (audioRecordCallback_ != null) {audioRecord_.RemoveCallback(audioRecordCallback_);audioRecordCallback_ = null;}audioRecord_ = null;}}if (libPublisher != null) {libPublisher.SmartPublisherStopPublisher(publisherHandle);}if (!isRTSPPublisherRunning) {if (publisherHandle != 0) {if (libPublisher != null) {libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}}}}//停止发布RTSP流private void stopRtspPublisher() {if(!isRTSPPublisherRunning){return;}if (!isPushingRtmp) {if (audioRecord_ != null) {Log.i(TAG, "stopRtspPublisher, call audioRecord_.StopRecording..");audioRecord_.Stop();if (audioRecordCallback_ != null) {audioRecord_.RemoveCallback(audioRecordCallback_);audioRecordCallback_ = null;}audioRecord_ = null;}}if (libPublisher != null) {libPublisher.StopRtspStream(publisherHandle);}if (!isPushingRtmp) {if (publisherHandle != 0) {if (libPublisher != null) {libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}}}}//停止RTSP服务private void stopRtspService() {if(!isRTSPServiceRunning){return;}if (libPublisher != null && rtsp_handle_ != 0) {libPublisher.StopRtspServer(rtsp_handle_);libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;}}

传递采集到的视频数据,摄像头数据采集,也可选用camera2的接口,对焦和体验更好:

	@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {pushFrameCount++;if ( pushFrameCount % 3000 == 0 ){Log.i("OnPre", "gc+");System.gc();Log.i("OnPre", "gc-");}if (data == null) {Parameters params = camera.getParameters();Size size = params.getPreviewSize();int bufferSize = (((size.width|0x1f)+1) * size.height * ImageFormat.getBitsPerPixel(params.getPreviewFormat())) / 8;camera.addCallbackBuffer(new byte[bufferSize]);} else {if(isPushingRtmp || isRTSPPublisherRunning){libPublisher.SmartPublisherOnCaptureVideoData(publisherHandle, data, data.length, pushCurrentCameraType, currentPushOrigentation);}camera.addCallbackBuffer(data);}} 

如果内网环境下,用轻量级RTSP服务的话,需判断对方有没有播放自己的流数据的话,可以通过获取RTSP会话数来判断是否链接。

	//当前RTSP会话数弹出框private void PopRtspSessionNumberDialog(int session_numbers) {final EditText inputUrlTxt = new EditText(this);inputUrlTxt.setFocusable(true);inputUrlTxt.setEnabled(false);String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;inputUrlTxt.setText(session_numbers_tag);AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);builderUrl.setTitle("内置RTSP服务").setView(inputUrlTxt).setNegativeButton("确定", null);builderUrl.show();}//获取RTSP会话数class ButtonGetRtspSessionNumbersListener implements OnClickListener {public void onClick(View v) {if (libPublisher != null && rtsp_handle_ != 0) {int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);PopRtspSessionNumberDialog(session_numbers);}}};

总结

Android平台的一对一互动,除了WebRTC外,在保证低延迟的前提下,RTMP或RTSP技术方案也是非常不错的选择。

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

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

相关文章

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

想要发免费的模板消息&#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;使用不同的场景 微信公众号小程序与服务号和订阅号有什么区别? …

Android国标接入终端实现GB28181实时位置(MobilePosition)上报

技术背景 在实现本文提到的Android平台国标GB28181接入终端的实时位置上报之前&#xff0c;之前已经完成了Android终端GB28181常规功能接入&#xff0c;采集到实时音视频数据&#xff0c;编码PS打包后&#xff0c;按需传到GB28281服务平台&#xff0c;媒体流支持最新GB28181-2…

基于RTMP的智慧数字人|AI数字人传输技术方案探讨

技术背景 随着智慧数字人、AI数字人的兴起&#xff0c;越来越多的公司着手构建​全息、真实感数字角色等技术合成的数字仿真人虚拟形象&#xff0c;通过“虚拟形象语音交互&#xff08;T-T-S、ASR&#xff09;自然语言理解&#xff08;NLU&#xff09;深度学习”&#xff0c;构…