转自:http://blog.csdn.net/nonmarking/article/details/47958395

本系列目前共三篇文章,后续还会更新

WebRTC VideoEngine超详细教程(一)——视频通话的基本流程

WebRTC VideoEngine超详细教程(二)——集成OPENH264编解码器

WebRTC VideoEngine超详细教程(三)——集成X264编码和ffmpeg解码

总述

在前一篇文章中,讲解了如何将OPENH264编解码器集成到WebRTC中,但是OPENH264只能编码baseline的H264视频,而且就编码质量而言,还是X264最好,本文就来讲解一下如何将X264编码器集成到WebRTC中,为了实现解码,同时要用到ffmpeg。总体流程和之前一样,分为重新封装编解码器和注册调用两大步骤,注册调用这一步没有任何不同,主要是重新封装这一步骤有较大区别。

重新封装X264编码功能

首先当然还是要下载X264源码编译出相应的库以供调用。在windows下使用mingw进行编译,再使用poxports工具导出库,最后得到libx264.dll和libx264.lib,同时把x264.h和x264_config.h总共四个文件放到工程目录下,并在项目属性中进行相应配置。
 
使用x264进行视频编码的基本流程如下
  1. #include <stdint.h>
  2. #include <stdio.h>
  3. #include <x264.h>
  4. int main( int argc, char **argv )
  5. {
  6. int width, height;
  7. x264_param_t param;
  8. x264_picture_t pic;
  9. x264_picture_t pic_out;
  10. x264_t *h;
  11. int i_frame = 0;
  12. int i_frame_size;
  13. x264_nal_t *nal;
  14. int i_nal;
  15. /* Get default params for preset/tuning */
  16. if( x264_param_default_preset( &param, "medium", NULL ) < 0 )
  17. goto fail;
  18. /* Configure non-default params */
  19. param.i_csp = X264_CSP_I420;
  20. param.i_width  = width;
  21. param.i_height = height;
  22. param.b_vfr_input = 0;
  23. param.b_repeat_headers = 1;
  24. param.b_annexb = 1;
  25. /* Apply profile restrictions. */
  26. if( x264_param_apply_profile( &param, "high" ) < 0 )
  27. goto fail;
  28. if( x264_picture_alloc( &pic, param.i_csp, param.i_width, param.i_height ) < 0 )
  29. goto fail;
  30. h = x264_encoder_open( &param);
  31. if( !h )
  32. goto fail;
  33. int luma_size = width * height;
  34. int chroma_size = luma_size / 4;
  35. /* Encode frames */
  36. for( ;; i_frame++ )
  37. {
  38. /* Read input frame */
  39. if( fread( pic.img.plane[0], 1, luma_size, stdin ) != luma_size )
  40. break;
  41. if( fread( pic.img.plane[1], 1, chroma_size, stdin ) != chroma_size )
  42. break;
  43. if( fread( pic.img.plane[2], 1, chroma_size, stdin ) != chroma_size )
  44. break;
  45. pic.i_pts = i_frame;
  46. i_frame_size = x264_encoder_encode( h, &nal, &i_nal, &pic, &pic_out );
  47. if( i_frame_size < 0 )
  48. goto fail;
  49. else if( i_frame_size )
  50. {
  51. if( !fwrite( nal->p_payload, i_frame_size, 1, stdout ) )
  52. goto fail;
  53. }
  54. }
  55. /* Flush delayed frames */
  56. while( x264_encoder_delayed_frames( h ) )
  57. {
  58. i_frame_size = x264_encoder_encode( h, &nal, &i_nal, NULL, &pic_out );
  59. if( i_frame_size < 0 )
  60. goto fail;
  61. else if( i_frame_size )
  62. {
  63. if( !fwrite( nal->p_payload, i_frame_size, 1, stdout ) )
  64. goto fail;
  65. }
  66. }
  67. x264_encoder_close( h );
  68. x264_picture_clean( &pic );
  69. return 0;
  70. }

还是一样,照葫芦画瓢,改写上一篇文章中提到的H264EncoderImpl类

首先是类的定义,去掉了原来的私有成员变量ISVCEncoder* encoder_,加入了以下几项,其他内容不变
  1. x264_picture_t pic;
  2. x264_picture_t pic_out;
  3. x264_t *encoder_;
  4. int i_frame = 0;//frame index
  5. x264_nal_t *nal;

相应的,构造函数和析构函数也要改变,这里就不赘述了,重点看InitEncode方法和Encode方法。

InitEncode方法的实现改写如下
  1. int H264EncoderImpl::InitEncode(const VideoCodec* inst,
  2. int number_of_cores,
  3. size_t max_payload_size) {
  4. if (inst == NULL) {
  5. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  6. }
  7. if (inst->maxFramerate < 1) {
  8. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  9. }
  10. // allow zero to represent an unspecified maxBitRate
  11. if (inst->maxBitrate > 0 && inst->startBitrate > inst->maxBitrate) {
  12. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  13. }
  14. if (inst->width < 1 || inst->height < 1) {
  15. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  16. }
  17. if (number_of_cores < 1) {
  18. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  19. }
  20. int ret_val = Release();
  21. if (ret_val < 0) {
  22. return ret_val;
  23. }
  24. /* Get default params for preset/tuning */
  25. x264_param_t param;
  26. ret_val = x264_param_default_preset(&param, "medium", NULL);
  27. if (ret_val != 0) {
  28. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  29. "H264EncoderImpl::InitEncode() fails to initialize encoder ret_val %d",
  30. ret_val);
  31. x264_encoder_close(encoder_);
  32. encoder_ = NULL;
  33. return WEBRTC_VIDEO_CODEC_ERROR;
  34. }
  35. /* Configure non-default params */
  36. param.i_csp = X264_CSP_I420;
  37. param.i_width = inst->width;
  38. param.i_height = inst->height;
  39. param.b_vfr_input = 0;
  40. param.b_repeat_headers = 1;
  41. param.b_annexb = 0;//这里设置为0,是为了使编码后的NAL统一有4字节的起始码,便于处理,否则会同时有3字节和4字节的起始码,很麻烦
  42. param.i_fps_num = 1;
  43. param.i_fps_num = codec_.maxFramerate;
  44. param.rc.i_bitrate = codec_.maxBitrate;
  45. /* Apply profile restrictions. */
  46. ret_val = x264_param_apply_profile(&param, "high");
  47. if (ret_val != 0) {
  48. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  49. "H264EncoderImpl::InitEncode() fails to initialize encoder ret_val %d",
  50. ret_val);
  51. x264_encoder_close(encoder_);
  52. encoder_ = NULL;
  53. return WEBRTC_VIDEO_CODEC_ERROR;
  54. }
  55. ret_val = x264_picture_alloc(&pic, param.i_csp, param.i_width, param.i_height);
  56. if (ret_val != 0) {
  57. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  58. "H264EncoderImpl::InitEncode() fails to initialize encoder ret_val %d",
  59. ret_val);
  60. x264_encoder_close(encoder_);
  61. encoder_ = NULL;
  62. return WEBRTC_VIDEO_CODEC_ERROR;
  63. }
  64. encoder_ = x264_encoder_open(&param);
  65. if (!encoder_){
  66. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  67. "H264EncoderImpl::InitEncode() fails to initialize encoder ret_val %d",
  68. ret_val);
  69. x264_encoder_close(encoder_);
  70. x264_picture_clean(&pic);
  71. encoder_ = NULL;
  72. return WEBRTC_VIDEO_CODEC_ERROR;
  73. }
  74. if (&codec_ != inst) {
  75. codec_ = *inst;
  76. }
  77. if (encoded_image_._buffer != NULL) {
  78. delete[] encoded_image_._buffer;
  79. }
  80. encoded_image_._size = CalcBufferSize(kI420, codec_.width, codec_.height);
  81. encoded_image_._buffer = new uint8_t[encoded_image_._size];
  82. encoded_image_._completeFrame = true;
  83. inited_ = true;
  84. WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, -1,
  85. "H264EncoderImpl::InitEncode(width:%d, height:%d, framerate:%d, start_bitrate:%d, max_bitrate:%d)",
  86. inst->width, inst->height, inst->maxFramerate, inst->startBitrate, inst->maxBitrate);
  87. return WEBRTC_VIDEO_CODEC_OK;
  88. }

Encode方法的实现改写如下

  1. int H264EncoderImpl::Encode(const I420VideoFrame& input_image,
  2. const CodecSpecificInfo* codec_specific_info,
  3. const std::vector<VideoFrameType>* frame_types) {
  4. if (!inited_) {
  5. return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
  6. }
  7. if (input_image.IsZeroSize()) {
  8. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  9. }
  10. if (encoded_complete_callback_ == NULL) {
  11. return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
  12. }
  13. VideoFrameType frame_type = kDeltaFrame;
  14. // We only support one stream at the moment.
  15. if (frame_types && frame_types->size() > 0) {
  16. frame_type = (*frame_types)[0];
  17. }
  18. bool send_keyframe = (frame_type == kKeyFrame);
  19. if (send_keyframe) {
  20. pic.b_keyframe = TRUE;
  21. WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, -1,
  22. "H264EncoderImpl::EncodeKeyFrame(width:%d, height:%d)",
  23. input_image.width(), input_image.height());
  24. }
  25. // Check for change in frame size.
  26. if (input_image.width() != codec_.width ||
  27. input_image.height() != codec_.height) {
  28. int ret = UpdateCodecFrameSize(input_image);
  29. if (ret < 0) {
  30. return ret;
  31. }
  32. }
  33. /* Read input frame */
  34. pic.img.plane[0] = const_cast<uint8_t*>(input_image.buffer(kYPlane));
  35. pic.img.plane[1] = const_cast<uint8_t*>(input_image.buffer(kUPlane));
  36. pic.img.plane[2] = const_cast<uint8_t*>(input_image.buffer(kVPlane));
  37. pic.i_pts = i_frame;
  38. int i_nal = 0;
  39. int i_frame_size = x264_encoder_encode(encoder_, &nal, &i_nal, &pic, &pic_out);
  40. if (i_frame_size < 0)
  41. {
  42. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  43. "H264EncoderImpl::Encode() fails to encode %d",
  44. i_frame_size);
  45. x264_encoder_close(encoder_);
  46. x264_picture_clean(&pic);
  47. encoder_ = NULL;
  48. return WEBRTC_VIDEO_CODEC_ERROR;
  49. }
  50. RTPFragmentationHeader frag_info;
  51. if (i_frame_size)
  52. {
  53. if (i_nal == 0) {
  54. return WEBRTC_VIDEO_CODEC_OK;
  55. }
  56. frag_info.VerifyAndAllocateFragmentationHeader(i_nal);
  57. encoded_image_._length = 0;
  58. uint32_t totalNaluIndex = 0;
  59. for (int nal_index = 0; nal_index < i_nal; nal_index++)
  60. {
  61. uint32_t currentNaluSize = 0;
  62. currentNaluSize = nal[nal_index].i_payload - 4; //x264_encoder_encode编码得到的nal单元是已经带有起始码的,此外,这里直接使用nal[index]即可,不必再使用x264_nal_encode函数
  63. memcpy(encoded_image_._buffer + encoded_image_._length, nal[nal_index].p_payload + 4, currentNaluSize);//encoded_image_中存有的是去掉起始码的数据
  64. encoded_image_._length += currentNaluSize;
  65. WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, -1,
  66. "H264EncoderImpl::Encode() nal_type %d, length:%d",
  67. nal[nal_index].i_type, encoded_image_._length);
  68. frag_info.fragmentationOffset[totalNaluIndex] = encoded_image_._length - currentNaluSize;
  69. frag_info.fragmentationLength[totalNaluIndex] = currentNaluSize;
  70. frag_info.fragmentationPlType[totalNaluIndex] = nal[nal_index].i_type;
  71. frag_info.fragmentationTimeDiff[totalNaluIndex] = 0;
  72. totalNaluIndex++;
  73. }
  74. }
  75. i_frame++;
  76. if (encoded_image_._length > 0) {
  77. encoded_image_._timeStamp = input_image.timestamp();
  78. encoded_image_.capture_time_ms_ = input_image.render_time_ms();
  79. encoded_image_._encodedHeight = codec_.height;
  80. encoded_image_._encodedWidth = codec_.width;
  81. encoded_image_._frameType = frame_type;
  82. // call back
  83. encoded_complete_callback_->Encoded(encoded_image_, NULL, &frag_info);
  84. }
  85. return WEBRTC_VIDEO_CODEC_OK;
  86. }

其他方法的实现均没有改变。

至此,X264编码器重新封装完毕,还是比较好理解的。

重新封装ffmpeg解码功能

首先还是一样,获得ffmpeg的头文件和库文件,加入工程中并进行相应设置,这里只需使用avcodec avformat avutil swscale四个库,头文件也可以做相应的删减。
ffmpeg解码的基本流程如下,实际集成之后是从WebRTC的EncodedImage& input_image中获得待解码数据的,所以不能使用常见的基于文件的解码流程
  1. AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
  2. AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
  3. avcodec_open2(codecCtx, codec, nil);
  4. char *videoData;
  5. int len;
  6. AVFrame *frame = av_frame_alloc();
  7. AVPacket packet;
  8. av_new_packet(&packet, len);
  9. memcpy(packet.data, videoData, len);
  10. int ret, got_picture;
  11. ret = avcodec_decode_video2(codecCtx, frame, &got_picture, &packet);
  12. if (ret > 0){
  13. if(got_picture){
  14. //进行下一步的处理
  15. }
  16. }

相应的,对H264DecoderImpl类的定义和各方法的实现要进行改写。
首先是类的定义,去掉了ISVCDecoder* decoder_,加入了以下私有成员变量

  1. AVCodecContext  *pCodecCtx;
  2. AVCodec           *pCodec;
  3. AVFrame   *pFrame, *pFrameYUV;
  4. AVPacket *packet;
  5. struct SwsContext *img_convert_ctx;
  6. uint8_t *decode_buffer;//存储最开始收到的SPS、PPS和IDR帧以便进行最开始的解码
  7. uint8_t *out_buffer;
  8. int framecnt = 0;
  9. int encoded_length = 0;
构造函数和析构函数的改写省略不表,重点看一下InitDecode方法和Decode方法
InitDecode方法改写如下
  1. int H264DecoderImpl::InitDecode(const VideoCodec* inst, int number_of_cores) {
  2. if (inst == NULL) {
  3. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  4. }
  5. int ret_val = Release();
  6. if (ret_val < 0) {
  7. return ret_val;
  8. }
  9. if (&codec_ != inst) {
  10. // Save VideoCodec instance for later; mainly for duplicating the decoder.
  11. codec_ = *inst;
  12. }
  13. pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
  14. pCodecCtx = avcodec_alloc_context3(pCodec);
  15. pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
  16. pCodecCtx->width = codec_.width;
  17. pCodecCtx->height = codec_.height;
  18. //pCodecCtx->bit_rate = codec_.targetBitrate*1000;
  19. pCodecCtx->time_base.num = 1;
  20. pCodecCtx->time_base.den = codec_.maxFramerate;
  21. if (pCodec == NULL){
  22. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  23. "H264DecoderImpl::InitDecode, Codec not found.");
  24. return WEBRTC_VIDEO_CODEC_ERROR;
  25. }
  26. if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
  27. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  28. "H264DecoderImpl::InitDecode, Could not open codec.");
  29. return WEBRTC_VIDEO_CODEC_ERROR;
  30. }
  31. inited_ = true;
  32. // Always start with a complete key frame.
  33. key_frame_required_ = true;
  34. WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, -1,
  35. "H264DecoderImpl::InitDecode(width:%d, height:%d, framerate:%d, start_bitrate:%d, max_bitrate:%d)",
  36. inst->width, inst->height, inst->maxFramerate, inst->startBitrate, inst->maxBitrate);
  37. return WEBRTC_VIDEO_CODEC_OK;
  38. }

Decode方法的实现改写如下

  1. int H264DecoderImpl::Decode(const EncodedImage& input_image,
  2. bool missing_frames,
  3. const RTPFragmentationHeader* fragmentation,
  4. const CodecSpecificInfo* codec_specific_info,
  5. int64_t /*render_time_ms*/) {
  6. if (!inited_) {
  7. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  8. "H264DecoderImpl::Decode, decoder is not initialized");
  9. return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
  10. }
  11. if (decode_complete_callback_ == NULL) {
  12. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  13. "H264DecoderImpl::Decode, decode complete call back is not set");
  14. return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
  15. }
  16. if (input_image._buffer == NULL) {
  17. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  18. "H264DecoderImpl::Decode, null buffer");
  19. return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
  20. }
  21. if (!codec_specific_info) {
  22. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  23. "H264EncoderImpl::Decode, no codec info");
  24. return WEBRTC_VIDEO_CODEC_ERROR;
  25. }
  26. if (codec_specific_info->codecType != kVideoCodecH264) {
  27. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  28. "H264EncoderImpl::Decode, non h264 codec %d", codec_specific_info->codecType);
  29. return WEBRTC_VIDEO_CODEC_ERROR;
  30. }
  31. WEBRTC_TRACE(webrtc::kTraceApiCall, webrtc::kTraceVideoCoding, -1,
  32. "H264DecoderImpl::Decode(frame_type:%d, length:%d",
  33. input_image._frameType, input_image._length);
  34. if (framecnt < 2)
  35. {//存储最开始的SPS PPS 和 IDR帧以便进行初始的解码
  36. memcpy(decode_buffer + encoded_length, input_image._buffer, input_image._length);
  37. encoded_length += input_image._length;
  38. framecnt++;
  39. }
  40. else
  41. {
  42. pFrame = av_frame_alloc();
  43. pFrameYUV = av_frame_alloc();
  44. out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
  45. avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
  46. img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
  47. pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
  48. if (framecnt == 2)
  49. {
  50. packet = (AVPacket *)av_malloc(sizeof(AVPacket));
  51. av_new_packet(packet, encoded_length);
  52. memcpy(packet->data, decode_buffer, encoded_length);
  53. av_free(decode_buffer);
  54. framecnt++;
  55. printf("\n\nLoading");
  56. }
  57. else
  58. {
  59. packet = (AVPacket *)av_malloc(sizeof(AVPacket));
  60. av_new_packet(packet, input_image._length);
  61. memcpy(packet->data, input_image._buffer, input_image._length);
  62. }
  63. int got_picture = 0;
  64. int ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  65. if (ret < 0){
  66. WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,
  67. "H264DecoderImpl::Decode, Decode Error.");
  68. return WEBRTC_VIDEO_CODEC_ERROR;
  69. }
  70. if (got_picture){
  71. sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
  72. pFrameYUV->data, pFrameYUV->linesize);
  73. int size_y = pFrameYUV->linesize[0] * pCodecCtx->height;
  74. int size_u = pFrameYUV->linesize[1] * pCodecCtx->height / 2;
  75. int size_v = pFrameYUV->linesize[2] * pCodecCtx->height / 2;
  76. decoded_image_.CreateFrame(size_y, static_cast<uint8_t*>(pFrameYUV->data[0]),
  77. size_u, static_cast<uint8_t*>(pFrameYUV->data[1]),
  78. size_v, static_cast<uint8_t*>(pFrameYUV->data[2]),
  79. pCodecCtx->width,
  80. pCodecCtx->height,
  81. pFrameYUV->linesize[0],
  82. pFrameYUV->linesize[1],
  83. pFrameYUV->linesize[2]);
  84. decoded_image_.set_timestamp(input_image._timeStamp);
  85. decode_complete_callback_->Decoded(decoded_image_);
  86. return WEBRTC_VIDEO_CODEC_OK;
  87. }
  88. else
  89. printf(".");
  90. av_free_packet(packet);
  91. }
  92. return WEBRTC_VIDEO_CODEC_OK;
  93. }

其他方法的实现保持不变,至此ffmpeg解码功能的重新封装也完成了。

 
从最后实现的效果来看,X264的视频质量的确是最好的,但是播放端的解码延时比较高,暂时还不清楚原因,希望了解的朋友指教。
本项目源代码

WebRTC VideoEngine超详细教程(三)——集成X264编码和ffmpeg解码的更多相关文章

  1. 数学规划求解器lp_solve超详细教程

    前言 最近小编学了运筹学中的单纯形法.于是,很快便按奈不住跳动的心.这不得不让我拿起纸和笔思考着,一个至关重要的问题:如何用单纯形法装一个完备的13? 恰巧,在我坐在图书馆陷入沉思的时候,一位漂亮的小 ...

  2. Github上传代码菜鸟超详细教程【转】

    最近需要将课设代码上传到Github上,之前只是用来fork别人的代码. 这篇文章写得是windows下的使用方法. 第一步:创建Github新账户 第二步:新建仓库 第三部:填写名称,简介(可选), ...

  3. 安装64位Oracle 10g超详细教程

    安装64位Oracle 10g超详细教程 1. 安装准备阶段 1.1 安装Oracle环境 经过上一篇博文的过程,已经完成了对Linux系统的安装,本例使用X-Manager来实现与Linux系统的连 ...

  4. NumPy 超详细教程(3):ndarray 的内部机理及高级迭代

    系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 ndarray 对象的内部机理 ...

  5. NumPy 超详细教程(2):数据类型

    系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 文章目录 NumPy 数据类型 ...

  6. NumPy 超详细教程(1):NumPy 数组

    系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 文章目录 Numpy 数组:n ...

  7. 【python】10分钟教你用python打造贪吃蛇超详细教程

    10分钟教你用python打造贪吃蛇超详细教程 在家闲着没妹子约, 刚好最近又学了一下python,听说pygame挺好玩的.今天就在家研究一下, 弄了个贪吃蛇出来.希望大家喜欢. 先看程序效果: 0 ...

  8. c++ 网络编程课设入门超详细教程 ---目录

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9663167.html c++ 网络编程(一)TCP/UDP windows/linux 下入门 ...

  9. c++ 网络编程(九)LINUX/windows-IOCP模型 多线程超详细教程及多线程实现服务端

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9661012.html 先讲Linux下(windows下在后面可以直接跳到后面看): 一.线程 ...

随机推荐

  1. 行为Behavior的使用

    原文地址:http://www.it610.com/article/4918541.htm 行为就是继承yii\base\behavior,可以绑定到任意yii\base\compent实例上,然后这 ...

  2. linux 软件安装

    A:RPM包,这种软件包就像windows的EXE安装文件一样,各种文件已经编译好,并打了包,哪个文件该放到哪个文件夹,都指定好了,安装非常方便,在图形界面里你只需要双击就能自动安装,如果在命令行模式 ...

  3. 6种编写HTML和CSS的最有效的方法

    感谢HTML5和CSS3,以及JavaScript,前端开发者有了大大的用武之地.大家都在用很多的工具和技术来武装自己,以加快前段的开发. 本文分享了6中最有效的方法,希望能提供你的效率,为你节约时间 ...

  4. git基础知识总结

    1,clone git clone https://github.com/KoMiles/helloword helloword 2,pull git pull 3,commit git commit ...

  5. editplus快捷键大全其他editplus快捷键

    editplus快捷键大全其他editplus快捷键,更多快捷键请参考以下文章:editplus快捷键大全之editplus搜索快捷键 editplus快捷键大全之editplus编辑快捷键 edit ...

  6. BC.36.Gunner(hash)

    Gunner  Accepts: 391  Submissions: 1397  Time Limit: 8000/4000 MS (Java/Others)  Memory Limit: 65536 ...

  7. 【Android代码片段之六】Toast工具类(实现带图片的Toast消息提示)

    转载请注明出处,原文网址:http://blog.csdn.net/m_changgong/article/details/6841266  作者:张燕广 实现的Toast工具类ToastUtil封装 ...

  8. [Effective JavaScript 笔记]第60条:支持方法链

    无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在 ...

  9. unity3d AssetBundle包加密

    原地址:http://www.cnblogs.com/88999660/archive/2013/03/15/2961587.html 保护资源管理文件的相关内容 Unity允许用户使用AssetBu ...

  10. 关于windows程序的学习及思考系列之一

    1.窗口类的注册 a.windows程序中最简单的就是创建一个简单的窗口,而窗口程序的创建是基于窗口类的,窗口类决定了处理窗口消息的过程函数. b.一个窗口类可以用于创建多个窗口,也就是说窗口是窗口类 ...