先列关键词:Android录制、SOCKET转发、.NET实时播放,有兴趣的请往下看。

这是一个大工程,兜兜转转,花了不少时间,千真万确,玩成功了。

这是一个小工程,和QQ视频聊天比起来还很low,真的只能玩玩。

不管是大工程还是小工程,相信我,到2015年1月1日为止,这是网上能给搜到的唯一一篇这么玩,且实现播放了的文章。

什么,你跟我提YUV?好吧,我承认这是能实现的。。。就是只怕4G网络环境都玩不转

闲话不提,先说具体思路:.NET客户端发出连接指令→服务器转发→Android端接收→手机端录制→服务器转发→.NET客户端播放。噢。。这还是闲话,而且有点复杂,进入主题。

按上面的流程,首先,网页端发送与Android进行视频的指令,这个其实就是个socket通信,发一条指令,这个指令包括视频命令的标识和相应的ANDROID客户端标识。所谓视频命令标识,是指服务器端能给识别出,这条信号是要转发给ANDROID端的,Android端要能识别出,这条信号,是为了要来录制视频传上去的。至于.NET客户端是什么玩意,怎么连接SOCKET服务器,就写了,ASP有ASP的搞法,WINFORM有WINFORM的搞法,SL有SL的搞法,在此就不表了,提醒一下,SL好像是要有一个安全验证的,如果我没记错。

然后是服务器转发,这个服务器我是采用windows service 写的,里面开两个socket服务,一个端口用于接收和发送指令之类的信号,一个端口专门转发视频信号。如果单功能的话其实可以只用一个端口的,我做的时候还有其他功能,为了避免传视频的时候其他阻塞其他功能的信号,就开了两个端口。C#中开SOCKET端口的方法:

private Socket GetSocketServer(int serverPort)
{Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socket.Bind(new IPEndPoint(IPAddress.Any, serverPort));socket.Listen(40);return socket;
}

转发视频指令代码:

 else if (ByteCompare(flag, MessageFlag.SVdCommand))
{try{Log.Write(0, "Client", this.ClientName + "发送视频指令");Client phoneClient = null;string phoneName = Encoding.UTF8.GetString(info);//找出相应的手机端for (int i = 0; i < this.Server.ClientList.Count; i++){if (this.Server.ClientList[i].ClientName == phoneName){phoneClient = this.Server.ClientList[i];break;}}if (phoneClient != null){phoneClient.CurrentSocket.Send(flag);Socket webMediaSocket = this.Server.MediaSocket.Accept();Log.Write(0, "Client", this.ClientName + "准备好接收视频信号");MediaClient webMediaClient = new MediaClient(this.Server, webMediaSocket);webMediaClient.ClientName = this.ClientName;//接收手机端的连接Socket phoneSocket = this.Server.MediaSocket.Accept();Log.Write(0, "Client", phoneClient.ClientName + "准备发送收视频信号");MediaClient phoneMediaClient = new MediaClient(this.Server, phoneSocket);phoneMediaClient.ClientName = phoneClient.ClientName;this.Server.MediaClientList.Add(phoneMediaClient);webMediaClient.OpClient = phoneMediaClient;phoneMediaClient.OpClient = webMediaClient;Thread t = new Thread(phoneMediaClient.ReceiveClient);Log.Write(0, "Client", phoneClient.ClientName + "开始发送收视频信号");t.Start();}else{Log.Write(1, "Client", this.ClientName + "发送视频指令时未找到对应的手机");}}catch (Exception ex){Log.Write(1, "Client", "请求视频指令时发生错误,请查看错误日志!");Log.WriteError(ex.ToString());}
}

ByteCompare是自定义的Byte数组比较的方法,其中的flag就是上文说过的指令,后面的则是预制的指令枚举。ClientList是连接到服务器的所有手机客户端,从中找出指定的客户端,发送信号。与此同时,.NET客户端连接上视频服务器,等待手机端连接上视频服务器。当两者都连上之后,给两者配对,在之后的岁月里,配对的客户端将通过客户端,手牵着手,同0共1。

接下来就是手机端接收了。接收也没什么好说的,就是SOCKET接收,接收完了就比较:

else if (CompareByte(flag, InfoFlags.SVdCommand))
{//接收视频指令if (videoListener != null) {Object source = new Object();videoListener.onVideoConnect(new BaseEvent(source));}
}

上面定义了一个事件监听,监听视频的连接和断开,监听内容:

SocketClient.setVideoListener(new VideoListener() {@Overridepublic void onVideoDisconnect(BaseEvent e) {    if (GlobalActivity.RecorderInstance != null) {GlobalActivity.RecorderInstance.StopRecord(null);}}@Overridepublic void onVideoConnect(BaseEvent e) {NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);int icon = R.drawable.message;Intent intent = new Intent(MainActivity.this,RecorderActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);Notification.Builder builder = new Notification.Builder(MainActivity.this);builder.setDefaults(Notification.DEFAULT_ALL).setTicker("接收到来自网页端的视频命令!").setContentTitle("视频命令").setContentText("接收到来自网页端的视频命令!接收请点击信息,拒绝请清除信息!").setSmallIcon(icon).setContentIntent(pendingIntent);Notification notification = builder.build();notification.flags = Notification.FLAG_AUTO_CANCEL;notificationManager.notify(0, notification);}
});

其实就是接收到之后,就给手机发一通知,点那个通知,就进入拍摄界面。

自然而然,就开始录制了,录制界面吧,直接看代码,结构简单至极,不占篇幅:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#0099cc"tools:context="com.ljnewmap.rlxj.RecorderActivity" ><SurfaceViewandroid:id="@+id/record_preview_surfaceView"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_gravity="center" /><ImageButtonandroid:id="@+id/startButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:layout_alignParentRight= "true"android:layout_centerVertical="true" android:background="@layout/img_stop_press"android:contentDescription="@string/app_name"android:onClick="StopRecord" /></RelativeLayout>

不得不说,还是相对布局比较好用,轻松自在就把按钮放在下面的中间了。。用线性布局简直复杂如狗。。。。。。

接下来自然而然就是录制的后台和上传了,录制采用H264编码,经过测试,480P的视频需要的网速是128K,就是1M网,基本上是3G网的上限,稍微降一些分辨率,降一些码率,应该是能符合现实需求的。上传当然采用SOCKET上传。坑爹的是,直接录制的H264码传上去是不能播放的,所以需要稍作处理。代码我会直接贴在下面,具体原理请见大神zblue78的博客:http://blog.csdn.net/zblue78/article/details/6078040 和http://blog.csdn.net/zblue78/article/details/6083374   我和大神不同的地方仅仅在于,他是直接把byte写成了文件,我则是将其通过socket发了出去。代码如下:

package com.ljnewmap.rlxj;import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;import com.ljnewmap.rlxj.R;
import com.ljnewmap.rlxj.dal.PPSandSPS;
import com.ljnewmap.rlxj.global.ExitApplication;
import com.ljnewmap.rlxj.global.Global;
import com.ljnewmap.rlxj.global.GlobalActivity;import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Bundle;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;/*** An example full-screen activity that shows and hides the system UI (i.e.* status bar and navigation/system bar) with user interaction.* * @see SystemUiHider*/
public class RecorderActivity extends Activity implements SurfaceHolder.Callback,
MediaRecorder.OnErrorListener,  MediaRecorder.OnInfoListener{private Socket ClientSocket;private MediaRecorder mMediaRecorder = null;private Camera camera;private boolean mMediaRecorderRecording = false; private SurfaceView mSurfaceView = null;  private SurfaceHolder mSurfaceHolder = null; private LocalSocket receiver, sender;  private LocalServerSocket lss;  private Thread t;  private boolean run = false;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 选择支持半透明模式,在有surfaceview的activity中使用。getWindow().setFormat(PixelFormat.TRANSLUCENT);//  去掉标题requestWindowFeature(Window.FEATURE_NO_TITLE);  // 全屏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  WindowManager.LayoutParams.FLAG_FULLSCREEN);setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);setContentView(R.layout.activity_recorder);mSurfaceView = (SurfaceView) this.findViewById(R.id.record_preview_surfaceView);mSurfaceHolder= mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this);ExitApplication.getInstance().addActivity(this);GlobalActivity.RecorderInstance = this;}private void ConectLocalSocket(){if (receiver == null) {receiver = new LocalSocket();}try {  lss = new LocalServerSocket("Local_Socket"); LocalSocketAddress localSocketAddress = new LocalSocketAddress("Local_Socket");receiver.connect(localSocketAddress);  receiver.setReceiveBufferSize(500000);  receiver.setSendBufferSize(500000);  sender = lss.accept();sender.setReceiveBufferSize(500000);sender.setSendBufferSize(500000);} catch (IOException e) {  return; }}private void startVideoRecording() {  t = new Thread() {  @Overridepublic void run() { try {while(ClientSocket == null){ClientSocket = new Socket(Global.Host, Global.Media_port);Thread.sleep(200);}} catch (Exception e) {}int frame_size = 1024;if(sender == null || receiver == null || lss == null){ConectLocalSocket();}byte[] buffer = new byte[1024*64];InputStream fis = null;//DataOutputStream outputStream = null;OutputStream outputStream = null;try {fis = receiver.getInputStream();outputStream = ClientSocket.getOutputStream();} catch (Exception e) {}initializeVideo();mMediaRecorder.start();  DataInputStream dis=new DataInputStream(fis);      try {  dis.read(buffer,0,44);  } catch (IOException e1) {  // TODO Auto-generated catch block  e1.printStackTrace();  }String sampleFilePath = Environment.getExternalStorageDirectory().getPath() +"/RLXJ/sample.h264";File samplefile = new File(sampleFilePath);if (!samplefile.exists()) {return;}byte[] h264sps = null;  byte[] h264pps = null;try {PPSandSPS PpSandSPS = new PPSandSPS(sampleFilePath);h264sps = PpSandSPS.SPS;h264pps = PpSandSPS.PPS;} catch (IOException e2) {// TODO Auto-generated catch blockreturn;}byte[] h264head={0,0,0,1};  try {  outputStream.write(h264head,0,h264head.length);outputStream.write(h264sps,0,h264sps.length);  outputStream.write(h264head,0,h264head.length);  outputStream.write(h264pps,0,h264pps.length);  } catch (IOException e1) {  // TODO Auto-generated catch block  e1.printStackTrace();  }  while (true)  {                    try {  //读取每场的长度 int h264length=dis.readInt();if (h264length > 50000 || h264length < 0) {while(true){byte byte1,byte2,byte3,byte4;byte1 =   dis.readByte();if(byte1 == 0){byte2 = dis.readByte();if(byte2 == 0){byte3 = dis.readByte();byte4 = dis.readByte();int value;    value = (int) ( ((byte1 & 0xFF)<<24)  |((byte2 & 0xFF)<<16)  |((byte3 & 0xFF)<<8)  |(byte4 & 0xFF)); if (value > 0 && value < 50000) {h264length = value;break;}}}}}int number =0;  outputStream.write(h264head,0,h264head.length);  while(number<h264length)  {  int lost=h264length-number;  int num = fis.read(buffer,0,frame_size<lost?frame_size:lost);  number+=num;  outputStream.write(buffer, 0, num);  }  } catch (Exception e) {  break;  }  } try {ClientSocket.shutdownInput();ClientSocket.shutdownOutput();ClientSocket.close();} catch (Exception e) {// TODO: handle exception}}};t.start();}  private boolean initializeVideo() {try {if (mSurfaceHolder == null)  return false;  mMediaRecorderRecording = true;  if (mMediaRecorder == null)  mMediaRecorder = new MediaRecorder();  else  mMediaRecorder.reset();  if(camera == null)camera = Camera.open(0);camera.unlock();mMediaRecorder.setCamera(camera);mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);if(profile != null){profile.videoFrameRate = 20;profile.videoBitRate = 1000000;profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;profile.videoFrameRate = 24;profile.videoCodec = MediaRecorder.VideoEncoder.H264;profile.audioCodec = MediaRecorder.AudioEncoder.AAC;mMediaRecorder.setProfile(profile);}// 预览mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());  //设置以流方式输出mMediaRecorder.setOutputFile(sender.getFileDescriptor());  //mMediaRecorder.setOutputFile( Environment.getExternalStorageDirectory().getPath() + "/Video/45.mp4");  try {  mMediaRecorder.setOnInfoListener(this);  mMediaRecorder.setOnErrorListener(this);  mMediaRecorder.prepare();} catch (Exception exception) { releaseMediaRecorder();  finish();  return false;  }  return true;  } catch (Exception e) {releaseMediaRecorder();  return false;}}private void releaseMediaRecorder() {  if (mMediaRecorder != null) {  if (mMediaRecorderRecording) {  try {  mMediaRecorder.setOnErrorListener(null);  mMediaRecorder.setOnInfoListener(null);  mMediaRecorder.stop();  } catch (RuntimeException e) {  System.out.println("stop fail: " + e.getMessage());  }  mMediaRecorderRecording = false;  }  mMediaRecorder.reset();  mMediaRecorder.release(); camera.lock();camera.release();mMediaRecorder = null;  }  }@Overridepublic void surfaceCreated(SurfaceHolder holder) {  // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder  mSurfaceHolder = holder;//initializeVideo();} @Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,  int height) {  // 将holder,这个holder为开始在oncreat里面取得的holder,将它赋给surfaceHolder  mSurfaceHolder = holder;if(camera == null){camera = Camera.open(0);}}   @Overridepublic void surfaceDestroyed(SurfaceHolder holder) {  // surfaceDestroyed的时候同时对象设置为null  mSurfaceView = null;  mSurfaceHolder = null;  mMediaRecorder = null; camera.release();} @Override  public void onInfo(MediaRecorder mr, int what, int extra) {  }  @Override  public void onError(MediaRecorder mr, int what, int extra) {  }  public void StopRecord(View view) {if(!run){startVideoRecording();run = true;}else {           if(t != null){t.interrupt();}releaseMediaRecorder();try {lss.close();receiver.close();sender.close();ClientSocket.close();} catch (Exception e) {}ExitApplication.getInstance().removeActivity(this);finish();}}@Override  public void onStart() {  super.onStart();  }  @Override  public void onResume() {  super.onResume();  }@Overrideprotected void onDestroy() {super.onDestroy();}}

不得不说,我这代码里面还有两点是和zblue78大神不同的,第一个地方是这句话:dis.read(buffer,0,44),读取不需要的字节。我见过有人用28,有人用32,当然我这边测试的几台机器,包括魅族MX3,小米2S,努比亚小牛,红米NOTE,都是44的。

第二个地方,就是下面这段:

if (h264length > 50000 || h264length < 0) {                        while(true){byte byte1,byte2,byte3,byte4;byte1 =   dis.readByte();if(byte1 == 0){byte2 = dis.readByte();if(byte2 == 0){byte3 = dis.readByte();byte4 = dis.readByte();int value;    value = (int) ( ((byte1 & 0xFF)<<24)  |((byte2 & 0xFF)<<16)  |((byte3 & 0xFF)<<8)  |(byte4 & 0xFF)); if (value > 0 && value < 50000) {h264length = value;break;}}}}
}

这一段是我后来发现,不知道为什么中间有一些杂波信号,我就把那些给滤掉了,当然也因此可能滤掉了一些正常的波,所以在播放视频的时候,偶尔会出现个一两幕马赛克供天下屌丝遐想。

其中SPS和PPS的自动获取,参看博客 http://blog.csdn.net/zgyulongfei/article/details/7538523 当然在这之前,得有个视频的预录制,就是录一小段H264码放在这,以便于获取SPS和PPS。代码如下:

public PPSandSPS(String fileName) throws IOException
{File file = new File(fileName);FileInputStream fis = new FileInputStream(file);  int fileLength = (int) file.length();  byte[] fileData = new byte[fileLength];  fis.read(fileData);  // 'a'=0x61, 'v'=0x76, 'c'=0x63, 'C'=0x43byte[] avcC = new byte[] { 0x61, 0x76, 0x63, 0x43 };  // avcC的起始位置  int avcRecord = 0;  for (int ix = 0; ix < fileLength; ++ix) {  if (fileData[ix] == avcC[0] && fileData[ix + 1] == avcC[1]  && fileData[ix + 2] == avcC[2]  && fileData[ix + 3] == avcC[3]) {  // 找到avcC,则记录avcRecord起始位置,然后退出循环。  avcRecord = ix + 4;  break;  }  }  if (0 == avcRecord) {  System.out.println("没有找到avcC,请检查文件格式是否正确");  }  // 加7的目的是为了跳过  // (1)8字节的 configurationVersion  // (2)8字节的 AVCProfileIndication  // (3)8字节的 profile_compatibility  // (4)8 字节的 AVCLevelIndication  // (5)6 bit 的 reserved  // (6)2 bit 的 lengthSizeMinusOne  // (7)3 bit 的 reserved  // (8)5 bit 的numOfSequenceParameterSets  // 共6个字节,然后到达sequenceParameterSetLength的位置  int spsStartPos = avcRecord + 6;  byte[] spsbt = new byte[] { fileData[spsStartPos],  fileData[spsStartPos + 1] };  int spsLength = bytes2Int(spsbt);  SPS = new byte[spsLength];  // 跳过2个字节的 sequenceParameterSetLength  spsStartPos += 2;  System.arraycopy(fileData, spsStartPos, SPS, 0, spsLength);  // 底下部分为获取PPS  // spsStartPos + spsLength 可以跳到pps位置  // 再加1的目的是跳过1字节的 numOfPictureParameterSets  int ppsStartPos = spsStartPos + spsLength + 1;  byte[] ppsbt = new byte[] { fileData[ppsStartPos],  fileData[ppsStartPos + 1] };  int ppsLength = bytes2Int(ppsbt);  PPS = new byte[ppsLength];  ppsStartPos += 2;  System.arraycopy(fileData, ppsStartPos, PPS, 0, ppsLength);fis.close();
}

服务器转发这部分就没啥好说的了,如上所说,同0共1,收到什么转发出什么,代码如下:

public void ReceiveClient()
{byte[] buffer = new byte[4096];int lenght = 0;try{lenght = CurrentSocket.Receive(buffer, buffer.Length, SocketFlags.None);}catch (Exception){}while (lenght>0){try{opClient.CurrentSocket.Send(buffer, 0, lenght, SocketFlags.None);lenght = CurrentSocket.Receive(buffer, buffer.Length, 0);}catch (Exception){}}if (CurrentSocket.Connected){base.DisconnectSocket(CurrentSocket);}base.CloseSocket(CurrentSocket);
}

然后就是解码了。。。这方面简直殚精竭虑,却一无所获,最后不得已,从VLC下手。。。当然,前提是,我知道VLC是可以播放码流写成的文件的。这个嘛,我用的是http://vlcdotnet.codeplex.com/  这个项目里面的控件,当然也是可以用VLC 的ACTIVEX插件的。。。但是这有一个问题,这玩意没法直接播放码流,只能播放rtp服务或者是文件,于是,我就写入临时文件,当接收到一定大小的时候,就可以开始播放了

接收:

private void RecieveAccept()
{while (true){Socket socket = ServerSocket.Accept();byte[] buffer = new byte[1024];int end = 0;try{end = socket.Receive(buffer, buffer.Length, 0);}catch (Exception){}Thread t = new Thread(Play);t.Start();while (end > 0){count += end;string filePath = AppDomain.CurrentDomain.BaseDirectory + "temp.h264";FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read);fs.Write(buffer, 0, end);fs.Flush();fs.Close();try{end = socket.Receive(buffer, buffer.Length, 0);}catch (Exception){}}socket.Disconnect(false);socket.Close();}
}

播放:

private void Play()
{while (count < 128 * 1024){Thread.Sleep(10);}PathMedia media = new PathMedia(AppDomain.CurrentDomain.BaseDirectory + "temp.h264");this.vlcControl1.Play(media);
}

其中的count是接到的数据的总长度。

到此差不多就写完了。。。应该没漏下什么吧,第一次写这么长的博客,请各位老大多多指正!要是有什么解码的方式,请指教!

Android端录制视频,.NET实时播放相关推荐

  1. Android MediaRecorder录制视频详细步骤

    使用MediaRecorder能够编写从设备麦克风与相机捕获音视频,保存音频并(使用MediaPlayer)进行播放的应用. 1.添加权限: <uses-permission android:n ...

  2. 使用手机摄像头实现视频监控实时播放

    使用手机摄像头实现视频监控实时播放 一.概述 视频监控实时播放的原理与目前较为流行的直播是一致的,所以采用直播的架构实现视频监控实时播放,流程图如下: #mermaid-svg-mUiqq5ywjTx ...

  3. 移动端加密视频的授权播放

    移动端加密视频的授权播放 Polyv的移动端加密视频由hls(m3u8文件)来实现. 移动端加密视频授权播放分三个级别 1.开放授权 开放授权意味着视频可以被随意观看,视频解密的key不被保护. 2. ...

  4. 如何做好 Android 端音视频测试?

    在用户眼中,优秀的音视频产品应该具有清晰.低延时.流畅.秒开.抗丢包.高音效等特征.为了满足用户以上要求,网易云信的工程师通过自建源站,在SDK端为了适应网络优化进行QoS优化,对视频编码器进行优化, ...

  5. Android开发之PCM录音实时播放的实现方法 | 边录音边播放 |PCM录音播放无延迟 | 录音无杂音 | 录音无噪音

    先说下录音得开启录音权限 <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后录音 ...

  6. android 边录制视频边写软字幕

    目前,对于边录制视频,边要显示时间戳的需求,都是通过用对应字符的bitmap图片的yuv数据,来替换每一帧yuv数据的像素点来实现的.这样做的坏处显而易见,这个时间戳数据,是硬生生的印在每一帧数据上的 ...

  7. Android端M3U8视频下载管理器----M3U8Manger

    转载请注明出处,大力哥的博客:http://blog.csdn.net/qq137722697 M3U8Manger (M3U8管理器) M3U8Manger ,android端M3U8文件下载管理器 ...

  8. js前端录制视频mp4本地播放转file

    前端录制视频 js export class VideoRecording { // 录视频 mediaRecorder: MediaRecorder | null; stream: MediaStr ...

  9. mediarecorder直播html5,html5 pc端录制视频+MediaStreamRecorder

    自己花了一天研究出来的  html 5 录制视频并上传到服务器  这方面资料太少了  尤其是中文资料 借鉴  SegmentFault  https://segmentfault.com/q/1010 ...

最新文章

  1. mysql的时间存储格式
  2. 体验.NET Core使用IKVM对接Java
  3. .NET三种异步模式(APM、EAP、TAP)
  4. PHPmyadmin 和 MySQL 的配置笔记
  5. Golang入门(4):并发
  6. 变形二叉树中节点的最大距离(树的最长路径)——非递归解法
  7. 通用数据链接(UDL)的用法
  8. linux如何检测文件完整,shell脚本实现linux系统文件完整性检测
  9. 听说你想当黑客,我只能帮你到这了
  10. VMware中安装linux系统(可视化界面centOS 7)
  11. 影视后期制作(Ae)
  12. 奥的斯维修服务器无响应,奥的斯GEN-2电梯故障现象:不定层的平层停梯,外呼无用断电或打检修会恢复还有运行至某层不开门自动去找平...
  13. chrome消除缓存的默认设置
  14. web前端基础案例-开发QQ空间旋转时光轴
  15. 【学习笔记】Android Fragments
  16. java 基础 api,Java基础——常用API
  17. mc服务器linux配置,详细教程——基于Centos搭建MC服务器(outdated)
  18. 2022-2028年全球与中国光谱比色计行业市场深度调研及投资预测分析
  19. openlayers加kriging出等值线图
  20. 大数据分析与应用(中级) 数据预处理与特征工程

热门文章

  1. NoSQL数据库入门与实践答案----第二章
  2. 可真刑!两高中生用 AI 生成涩图,疯狂变现
  3. 广度优先算法(BFS)
  4. 中国邮路问题 ZOJ1903
  5. starrocks部署准备
  6. 维吉尼亚密码Java实现
  7. PHP项目——外卖点餐系统后台管理解析
  8. Oracle存储过程-入门教程
  9. oracle 中的 CONCAT,substring ,MINUS 用法
  10. C++猴子摘桃--递归实现