转载:http://blog.csdn.net/ManagerUser/article/details/76087151

一、前言

        SRS流媒体服务器支持rtmp协议,但是rtmp协议仅仅支持PC直播。移动端直播需要HLS协议,HLS协议是苹果公司开发出来的,用于移动端视频直播,Android也对HLS做了友好支持。所以,SRS流媒体服务器支持rtmp协议和hls协议,满足了PC和移动端直播要求。
     HLS协议有两个关键文件:.m3u8文件和.ts文件:
  • .m3u8文件:播放控制文件,存放了地址和播放参数。
  • .ts文件:真正存储视频文件。
相关HLS协议详细说明参见:https://www.jianshu.com/p/2ce402a485ca。
       SRS流媒体接受到通过rtmp传输协议传输的编码格式为H264/AAC(注意:HLS协议只支持Video编码:H264;Audio编码:AAC/mp3)音视频数据,进行切片成.m3u8文件和.ts文件,存储在磁盘或者内存当中(注意:一般为了提高cpu使用率,将.m3u8和.ts文件存储在内存中)。再通过nginx分发到端(注意:nginx工作目录要和存储.m3u8路径一致)。
       HLS切片处理,其实就是ts编码的处理,通过将H264/AAC编码数据按照TS协议来分成一个一个的TS包。TS协议规定,TS首包内容为PAT(Program Association Table 节目关联表)表,其PID为0x0;接下来为PMT(Program Map Table 节目映射表)表,其PID为0x1001。其次,视频帧PID为:0x100,音频PID为:0x101。后面TS包为实际音视频数据。

二、代码分析

SRS源码相关其他总结:
       SRS(simple-rtmp-server)流媒体服务器源码分析--系统启动
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
       SRS(simple-rtmp-server)流媒体服务器源码分析--HLS切片

SRS源码HLS处理框架如下:

接着SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play分析,在接受到rtmp信息之后,进行HLS处理。

[cpp] view plain copy
  1. int SrsSource::on_video_imp(SrsSharedPtrMessage* msg)

在函数中,除了转发给client和forward之外,还有HLS处理。注意:在HLS这一讲中,只分析跟video相关的代码,audio类同。

[cpp] view plain copy
  1. #ifdef SRS_AUTO_HLS
  2. if ((ret = hls->on_video(msg, is_sequence_header)) != ERROR_SUCCESS) {
  3. // apply the error strategy for hls.
  4. // @see https://github.com/ossrs/srs/issues/264
  5. std::string hls_error_strategy = _srs_config->get_hls_on_error(_req->vhost);
  6. if (srs_config_hls_is_on_error_ignore(hls_error_strategy)) {
  7. srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret);
  8. // unpublish, ignore ret.
  9. hls->on_unpublish();
  10. // ignore.
  11. ret = ERROR_SUCCESS;
  12. } else if (srs_config_hls_is_on_error_continue(hls_error_strategy)) {
  13. if (srs_hls_can_continue(ret, cache_sh_video, msg)) {
  14. ret = ERROR_SUCCESS;
  15. } else {
  16. srs_warn("hls continue video failed. ret=%d", ret);
  17. return ret;
  18. }
  19. } else {
  20. srs_warn("hls disconnect publisher for video error. ret=%d", ret);
  21. return ret;
  22. }
  23. }
  24. #endif

进入on_video(),

[cpp] view plain copy
  1. int SrsHls::on_video(SrsSharedPtrMessage* shared_video, bool is_sps_pps)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. if (!hls_enabled) {
  5. return ret;
  6. }
  7. // update the hls time, for hls_dispose.
  8. last_update_time = srs_get_system_time_ms();
  9. SrsSharedPtrMessage* video = shared_video->copy();
  10. SrsAutoFree(SrsSharedPtrMessage, video);
  11. // user can disable the sps parse to workaround when parse sps failed.
  12. // @see https://github.com/ossrs/srs/issues/474
  13. /*
  14. SPS:
  15. H.264中定义的sequence parameter sets中包括了一个图像序列的所有信息.它也是H.264的基础之一
  16. PPS:
  17. H.264中定义的picture parameter sets中包括了一个图像的所有切片信息.它也是H.264的基础之一
  18. */
  19. if (is_sps_pps) {
  20. codec->avc_parse_sps = _srs_config->get_parse_sps(_req->vhost);
  21. }
  22. sample->clear();
  23. //检查H264编码,序列头
  24. if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
  25. srs_error("hls codec demux video failed. ret=%d", ret);
  26. return ret;
  27. }
  28. srs_info("video decoded, type=%d, codec=%d, avc=%d, cts=%d, size=%d, time=%"PRId64,
  29. sample->frame_type, codec->video_codec_id, sample->avc_packet_type, sample->cts, video->size, video->timestamp);
  30. // ignore info frame,
  31. // @see https://github.com/ossrs/srs/issues/288#issuecomment-69863909
  32. if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) {
  33. return ret;
  34. }
  35. if (codec->video_codec_id != SrsCodecVideoAVC) {
  36. return ret;
  37. }
  38. // ignore sequence header
  39. if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame
  40. && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
  41. return hls_cache->on_sequence_header(muxer);
  42. }
  43. // rtmp抖动矫正
  44. // TODO: FIXME: config the jitter of HLS.
  45. if ((ret = jitter->correct(video, SrsRtmpJitterAlgorithmOFF)) != ERROR_SUCCESS) {
  46. srs_error("rtmp jitter correct video failed. ret=%d", ret);
  47. return ret;
  48. }
  49. // PAR(pixel aspect ratio):像素横纵比 方形为1,长方形小于1
  50. // DAR(display aspect ratio):显示比例 16:9   4:3
  51. /*
  52. DTS:Decode Time Stamp.DTS主要是标识读入内存的字节流在什么时候开始送人解码器中进行解码
  53. DTS主要是用于视频解码,在解码阶段使用
  54. PTS主要用于视频同步和输出,在显示阶段使用,在没有B frame的情况下,DTS和PTS的输出顺序是一样的
  55. */
  56. int64_t dts = video->timestamp * 90;
  57. stream_dts = dts;
  58. if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
  59. srs_error("hls cache write video failed. ret=%d", ret);
  60. return ret;
  61. }
  62. // pithy print message.
  63. hls_show_mux_log();
  64. return ret;
  65. }

在该函数中做了几个工作:

1、获取H264编码信息SPS和PPS,注意:SPS和PPS不是每个流中都会有的,它只包含在头帧当中。

2、检测视频压缩编码格式:必须为H264,否则直接退出。

3、RTMP抖动矫正(不清楚做了些什么事,不管了)

4、HLS切片处理。

以上4点,前三点不在这里详细说明。只看HLS切片部分:

[cpp] view plain copy
  1. int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. // write video to cache.
  5. if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
  6. return ret;
  7. }
  8. // when segment overflow, reap if possible.
  9. if (muxer->is_segment_overflow()) {
  10. // do reap ts if any of:
  11. //      a. wait keyframe and got keyframe.
  12. //      b. always reap when not wait keyframe.
  13. if (!muxer->wait_keyframe() || sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) {
  14. // reap the segment, which will also flush the video.
  15. if ((ret = reap_segment("video", muxer, cache->video->dts)) != ERROR_SUCCESS) {
  16. return ret;
  17. }
  18. }
  19. }
  20. // flush video when got one
  21. if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) {
  22. srs_error("m3u8 muxer flush video failed. ret=%d", ret);
  23. return ret;
  24. }
  25. return ret;
  26. }

该函数做一下两点工作:

1、首次或者.ts文件时间溢出时,进入reap_segment()函数,该函数主要负责.m3u8和.ts文件管理(创建,打开,关闭)。注意:.m3u8文件是在ts文件写入完毕之后,在关闭操作中一次性将播放参数,ts地址等等参数写入。

2、其他时间,直接进入后面flush_video()函数,该函数负责ts流编码和.ts文件写入。

其中ts流编码设计到很多知识,大家自行查看相关知识:http://blog.csdn.net/u013354805/article/details/51578457

[cpp] view plain copy
  1. int SrsHlsMuxer::flush_video(SrsTsCache* cache)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. // if current is NULL, segment is not open, ignore the flush event.
  5. if (!current) {
  6. srs_warn("flush video ignored, for segment is not open.");
  7. return ret;
  8. }
  9. if (!cache->video || cache->video->payload->length() <= 0) {
  10. return ret;
  11. }
  12. srs_assert(current);
  13. //更新该ts文件持续时间
  14. // update the duration of segment.
  15. current->update_duration(cache->video->dts);
  16. //贴片处理
  17. if ((ret = current->muxer->write_video(cache->video)) != ERROR_SUCCESS) {
  18. return ret;
  19. }
  20. // write success, clear and free the msg
  21. srs_freep(cache->video);
  22. return ret;
  23. }

进入编码函数

[cpp] view plain copy
  1. int SrsTsContext::encode(SrsFileWriter* writer, SrsTsMessage* msg, SrsCodecVideo vc, SrsCodecAudio ac)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. SrsTsStream vs, as;
  5. int16_t video_pid = 0, audio_pid = 0;
  6. switch (vc) {
  7. case SrsCodecVideoAVC:
  8. vs = SrsTsStreamVideoH264;
  9. video_pid = TS_VIDEO_AVC_PID;
  10. break;
  11. case SrsCodecVideoDisabled:
  12. vs = SrsTsStreamReserved;
  13. break;
  14. case SrsCodecVideoReserved:
  15. case SrsCodecVideoReserved1:
  16. case SrsCodecVideoReserved2:
  17. case SrsCodecVideoSorensonH263:
  18. case SrsCodecVideoScreenVideo:
  19. case SrsCodecVideoOn2VP6:
  20. case SrsCodecVideoOn2VP6WithAlphaChannel:
  21. case SrsCodecVideoScreenVideoVersion2:
  22. vs = SrsTsStreamReserved;
  23. break;
  24. }
  25. switch (ac) {
  26. case SrsCodecAudioAAC:
  27. as = SrsTsStreamAudioAAC;
  28. audio_pid = TS_AUDIO_AAC_PID;
  29. break;
  30. case SrsCodecAudioMP3:
  31. as = SrsTsStreamAudioMp3;
  32. audio_pid = TS_AUDIO_MP3_PID;
  33. break;
  34. case SrsCodecAudioDisabled:
  35. as = SrsTsStreamReserved;
  36. break;
  37. case SrsCodecAudioReserved1:
  38. case SrsCodecAudioLinearPCMPlatformEndian:
  39. case SrsCodecAudioADPCM:
  40. case SrsCodecAudioLinearPCMLittleEndian:
  41. case SrsCodecAudioNellymoser16kHzMono:
  42. case SrsCodecAudioNellymoser8kHzMono:
  43. case SrsCodecAudioNellymoser:
  44. case SrsCodecAudioReservedG711AlawLogarithmicPCM:
  45. case SrsCodecAudioReservedG711MuLawLogarithmicPCM:
  46. case SrsCodecAudioReserved:
  47. case SrsCodecAudioSpeex:
  48. case SrsCodecAudioReservedMP3_8kHz:
  49. case SrsCodecAudioReservedDeviceSpecificSound:
  50. as = SrsTsStreamReserved;
  51. break;
  52. }
  53. if (as == SrsTsStreamReserved && vs == SrsTsStreamReserved) {
  54. ret = ERROR_HLS_NO_STREAM;
  55. srs_error("hls: no video or audio stream, vcodec=%d, acodec=%d. ret=%d", vc, ac, ret);
  56. return ret;
  57. }
  58. // when any codec changed, write PAT/PMT table.
  59. if (vcodec != vc || acodec != ac) {
  60. vcodec = vc;
  61. acodec = ac;
  62. if ((ret = encode_pat_pmt(writer, video_pid, vs, audio_pid, as)) != ERROR_SUCCESS) {
  63. return ret;
  64. }
  65. }
  66. // encode the media frame to PES packets over TS.
  67. if (msg->is_audio()) {
  68. return encode_pes(writer, msg, audio_pid, as, vs == SrsTsStreamReserved);
  69. } else {
  70. return encode_pes(writer, msg, video_pid, vs, vs == SrsTsStreamReserved);
  71. }
  72. }

该函数内容有点多:

1、根据音视频类型,获取不同的PID(在TS协议当中,TS包包含了音视频数据,它们是通过TS头协议PID来区分的)

2、TS编码,PAT帧,PMT帧(首帧TS流)

3、TS编码,音视频数据编码(TS流数据部分)

可以先进入encode_pat_pmt()函数函数中看看:

[cpp] view plain copy
  1. int SrsTsContext::encode_pat_pmt(SrsFileWriter* writer, int16_t vpid, SrsTsStream vs, int16_t apid, SrsTsStream as)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. if (vs != SrsTsStreamVideoH264 && as != SrsTsStreamAudioAAC && as != SrsTsStreamAudioMp3) {
  5. ret = ERROR_HLS_NO_STREAM;
  6. srs_error("hls: no pmt pcr pid, vs=%d, as=%d. ret=%d", vs, as, ret);
  7. return ret;
  8. }
  9. int16_t pmt_number = TS_PMT_NUMBER;
  10. int16_t pmt_pid = TS_PMT_PID;
  11. if (true) {
  12. // 创建一个PAT(节目关联表)
  13. SrsTsPacket* pkt = SrsTsPacket::create_pat(this, pmt_number, pmt_pid);
  14. SrsAutoFree(SrsTsPacket, pkt);
  15. // TS包188字节,其中头字节(4字节,184个字节)。加上16字节CRC,总共204字节
  16. char* buf = new char[SRS_TS_PACKET_SIZE];
  17. SrsAutoFreeA(char, buf);
  18. // set the left bytes with 0xFF.
  19. int nb_buf = pkt->size();
  20. srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
  21. memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
  22. SrsStream stream;
  23. if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
  24. return ret;
  25. }
  26. // 编码
  27. if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
  28. srs_error("ts encode ts packet failed. ret=%d", ret);
  29. return ret;
  30. }
  31. if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
  32. srs_error("ts write ts packet failed. ret=%d", ret);
  33. return ret;
  34. }
  35. }
  36. if (true) {
  37. //创建一个PMT
  38. SrsTsPacket* pkt = SrsTsPacket::create_pmt(this, pmt_number, pmt_pid, vpid, vs, apid, as);
  39. SrsAutoFree(SrsTsPacket, pkt);
  40. char* buf = new char[SRS_TS_PACKET_SIZE];
  41. SrsAutoFreeA(char, buf);
  42. // set the left bytes with 0xFF.
  43. int nb_buf = pkt->size();
  44. srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
  45. memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
  46. SrsStream stream;
  47. if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
  48. return ret;
  49. }
  50. // 编码
  51. if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
  52. srs_error("ts encode ts packet failed. ret=%d", ret);
  53. return ret;
  54. }
  55. if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
  56. srs_error("ts write ts packet failed. ret=%d", ret);
  57. return ret;
  58. }
  59. }
  60. // When PAT and PMT are writen, the context is ready now.
  61. ready = true;
  62. return ret;
  63. }

上面代码创建PAT和PMT两个TS包。将TS文件前两个包解析,如下:

再来看encode_pes()函数

[cpp] view plain copy
  1. int SrsTsContext::encode_pes(SrsFileWriter* writer, SrsTsMessage* msg, int16_t pid, SrsTsStream sid, bool pure_audio)
  2. {
  3. int ret = ERROR_SUCCESS;
  4. // Sometimes, the context is not ready(PAT/PMT write failed), error in this situation.
  5. if (!ready) {
  6. ret = ERROR_TS_CONTEXT_NOT_READY;
  7. srs_error("TS: context not ready, ret=%d", ret);
  8. return ret;
  9. }
  10. if (msg->payload->length() == 0) {
  11. return ret;
  12. }
  13. if (sid != SrsTsStreamVideoH264 && sid != SrsTsStreamAudioMp3 && sid != SrsTsStreamAudioAAC) {
  14. srs_info("ts: ignore the unknown stream, sid=%d", sid);
  15. return ret;
  16. }
  17. SrsTsChannel* channel = get(pid);
  18. srs_assert(channel);
  19. char* start = msg->payload->bytes();
  20. char* end = start + msg->payload->length();
  21. char* p = start;
  22. while (p < end) {
  23. SrsTsPacket* pkt = NULL;
  24. if (p == start) {
  25. // write pcr according to message.
  26. bool write_pcr = msg->write_pcr;
  27. // for pure audio, always write pcr.
  28. // TODO: FIXME: maybe only need to write at begin and end of ts.
  29. if (pure_audio && msg->is_audio()) {
  30. write_pcr = true;
  31. }
  32. // it's ok to set pcr equals to dts,
  33. // @see https://github.com/ossrs/srs/issues/311
  34. // Fig. 3.18. Program Clock Reference of Digital-Video-and-Audio-Broadcasting-Technology, page 65
  35. // In MPEG-2, these are the "Program Clock Refer- ence" (PCR) values which are
  36. // nothing else than an up-to-date copy of the STC counter fed into the transport
  37. // stream at a certain time. The data stream thus carries an accurate internal
  38. // "clock time". All coding and de- coding processes are controlled by this clock
  39. // time. To do this, the receiver, i.e. the MPEG decoder, must read out the
  40. // "clock time", namely the PCR values, and compare them with its own internal
  41. // system clock, that is to say its own 42 bit counter.
  42. int64_t pcr = write_pcr? msg->dts : -1;
  43. // TODO: FIXME: finger it why use discontinuity of msg.
  44. pkt = SrsTsPacket::create_pes_first(this,
  45. pid, msg->sid, channel->continuity_counter++, msg->is_discontinuity,
  46. pcr, msg->dts, msg->pts, msg->payload->length()
  47. );
  48. } else {
  49. pkt = SrsTsPacket::create_pes_continue(this,
  50. pid, msg->sid, channel->continuity_counter++
  51. );
  52. }
  53. SrsAutoFree(SrsTsPacket, pkt);
  54. char* buf = new char[SRS_TS_PACKET_SIZE];
  55. SrsAutoFreeA(char, buf);
  56. // set the left bytes with 0xFF.
  57. int nb_buf = pkt->size();
  58. srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
  59. int left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf);
  60. int nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left;
  61. if (nb_stuffings > 0) {
  62. // set all bytes to stuffings.
  63. memset(buf, 0xFF, SRS_TS_PACKET_SIZE);
  64. // padding with stuffings.
  65. pkt->padding(nb_stuffings);
  66. // size changed, recalc it.
  67. nb_buf = pkt->size();
  68. srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
  69. left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf);
  70. nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left;
  71. srs_assert(nb_stuffings == 0);
  72. }
  73. memcpy(buf + nb_buf, p, left);
  74. p += left;
  75. SrsStream stream;
  76. if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
  77. return ret;
  78. }
  79. if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
  80. srs_error("ts encode ts packet failed. ret=%d", ret);
  81. return ret;
  82. }
  83. if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) {
  84. srs_error("ts write ts packet failed. ret=%d", ret);
  85. return ret;
  86. }
  87. }
  88. return ret;
  89. }

编码成TS包,写入TS文件。

三、总结

1、TS编码,实际是按照TS协议来重新整理数据,TS协议是传输协议。TS协议传输的音视频数据压缩格式还是H264/AAC。

2、TS切片,实际是按照TS协议要求的字段长度对音视频数据进行分包处理。

3、TS文件,TS包存储的位置。又涉及到HLS协议和TS协议关系。

SRS 代码分析【HLS切片】相关推荐

  1. SRS 代码分析【mpeg-ts解析】

    SRS 代码分析[mpeg-ts解析] 1.SrsTsContext的decode接口定义如下: int SrsTsContext::decode(SrsBuffer* stream, ISrsTsH ...

  2. SRS 代码分析【保存MP3音频文件】

    1. SRS 对MP3音频文件的保存首先调用SrsMp3Transmuxer::write_header()写入头部信息,函数定义如下: int SrsMp3Transmuxer::write_hea ...

  3. Vision Transformer(VIT)代码分析——保姆级教程

    目录 前言 一.代码分析 1.1.DropPath模块 1.2.Patch Embeding 1.3.Multi-Head Attention 1.4.MLP 1.5.Block 1.6.Vision ...

  4. 静态代码分析工具列表分析---代码分析工具列表(30款工具)

    本文是一个静态代码分析工具的清单,共有30个工具.包括4个.NET工具.2个Ada工具.7个C++工具.4个Java工具.2个JavaScript工具.1个Opa工具.2个Packaging工具.3个 ...

  5. 静态代码分析工具清单:开源篇(各语言)

    本文是一个静态代码分析工具的清单,共有26个工具.包括4个.NET工具.2个Ada工具.7个C++工具.4个Java工具.2个JavaScript工具.1个Opa工具.2个Packaging工具.3个 ...

  6. 静态代码分析工具清单:开源篇

    http://hao.jobbole.com/static_code_analysis_tool_list_opensource_lang/?utm_source=blog.jobbole.com&a ...

  7. OAI项目GDB调试及代码分析

    OAI项目GDB调试及代码分析 如果想使用GDB调试工具对项目进行调试,首先需要在编译时加入调试信息. 在完成之前的对eNB和UE的编译之后,使用作者写的编译脚本,同时加上-g选项加入调试信息 ./c ...

  8. NLP-生成模型-2017-Transformer(二):Transformer各模块代码分析

    一.WordEmbedding层模块(文本嵌入层) Embedding Layer(文本嵌入层)的作用:无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 由一维转为多维 ...

  9. h5ai界面修改_H5ai修改版,带HTML5视频播放器DPlayer,并支持hls切片播放

    简介 该源码由LOC的冻猫修改,将H5ai的视频播放器替换成了DPlayer.快进什么的方便些,也可以调播放速度.想加弹幕之类的可以自己改代码,3757行里面研究吧. 更新 [2019.6.14] 1 ...

最新文章

  1. 重读【代码整洁之道】
  2. Web Bundler CheatSheet, 选择合适的构建打包工具
  3. C++11 并发指南三(std::mutex 详解)
  4. 喜讯,公司换宽屏液晶显示器了
  5. 好好学习网--2009年十大新兴企业技术:MapReduce折桂
  6. 一线上nagios监控参数
  7. 区块链BaaS云服务(2)亚马逊 Amazon Managed Blockchain
  8. COMMCONFIG进行配置的WIN32 API
  9. Android开发:自定义GridView/ListView数据源
  10. 使用QT的一些小Tipster
  11. Linux服务器 | 事件处理模式:Reactor模式、Proactor模式
  12. linux主机基本情况,查看linux主机系统基本信息.pdf
  13. 云原生数据湖解决方案打破数据孤岛,大数据驱动互娱行业发展
  14. 怎样永久更改嵌入式linux系统ip,如何修改嵌入式系统IP
  15. 软件测试必问必背面试题
  16. 软件工程——螺旋模型
  17. Windows上搭建SFTP服务器
  18. 阻塞IO和NIO的区别
  19. win10计算机禁用用户账户控制,win10系统在关闭了用户账户控制的情况下无法打开... 的解决方法...
  20. 7天内我面试了10家公司,如何从命中率0%到命中率至70%?

热门文章

  1. Adobe带你解锁办会新技能
  2. web安全之Webshell管理工具
  3. java基于quasar实现协程池
  4. 2021-02-28 Matlab优化拟合曲线
  5. 爱玩手机的猫git学习笔记(持续更新)
  6. net面试整试题及参考答案【转】
  7. 【源码好又多】springboot后台框架
  8. confusion_matrix
  9. MFCC和fbank的区别
  10. html让gif图片暂停,控制gif图片播放暂停插件-jquery.gif.js