目录

一、MediaCodec编码音频

创建音频编码器,指定AAC格式,采样率44100,码率64_000,单声道;

创建AudioRecord录音对象,设置参数与编码器对应;

启动编码器和录音器;

循环从录音器中读取PCM格式的byte数组,放入编码器的输入队列;

循环从编码器的输入队列中读取数据,获得编码好的AAC格式的byte数组,等待后续rtmp封包用。

二、MediaCodec编码视频

申请录屏权限,获取MediaProjection对象

创建视频编码器

将编码器的surface传给MediaProjection对象构建虚拟显示

循环从编码器的输出队列读取数据

三、H264裸流格式分析

四、FLV封装格式分析

FLV文件总体格式:

FLV文件的详细结构。

音频Tag Data格式:

视频Tag Data格式分析:

五、利用librtmp封包发送

librtmp的工作流程:

视频编码器中添加:

音频编码器中添加:

利用librtmp连接流:

音频封包:

视频封包:

1.判断视频类型,是否为AVC序列头:

2.获取SPS和PPS信息:

3.封装AVC序列头信息:

4.封装普通NAL单元信息:

利用librtmp发包:

参考文章链接:

Demo下载链接:


一、MediaCodec编码音频

  • 创建音频编码器,指定AAC格式,采样率44100,码率64_000,单声道;

            //创建编码器MediaFormat mediaFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,44100, 1);//编码规格,可以看成质量mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);//码率mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64_000);mediacodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);mediacodec.configure(mediaFormat,null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  • 创建AudioRecord录音对象,设置参数与编码器对应;

            //创建录音对象minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100,AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize);
  • 启动编码器和录音器;

  • 循环从录音器中读取PCM格式的byte数组,放入编码器的输入队列;

            //从录音器读取数据readIndex = audioRecord.read(buffer, 0, 1024);if (readIndex <= 0){continue;}//获取编码器输入队列的可用位置下标inputBufferIndex = mediacodec.dequeueInputBuffer(0);if (inputBufferIndex >= 0) {//获取输入队列inputBuffer = mediacodec.getInputBuffer(inputBufferIndex);inputBuffer.clear();//避免缓冲队列有冗余数据,先clear一下Log.d("zhangdi", "length = " + buffer.length + " readIndex=" + readIndex);inputBuffer.put(buffer);//将需要编码的数据放入队列缓冲区//将缓冲队列放回编码器mediacodec.queueInputBuffer(inputBufferIndex, 0, readIndex,System.nanoTime() / 1000, 0);}
  • 循环从编码器的输入队列中读取数据,获得编码好的AAC格式的byte数组,等待后续rtmp封包用。

            //获取编码器输入队列的可用下标位置,10为等待超时时间outputBufferIndex = mediacodec.dequeueOutputBuffer(bufferInfo, 10);while (outputBufferIndex >= 0 && isRecording) {//获取输出缓冲区outputBuffer = mediacodec.getOutputBuffer(outputBufferIndex);outBuffer = new byte[bufferInfo.size];//获取编码好的AAC数据outputBuffer.get(outBuffer);//rtmp封包//释放缓冲区mediacodec.releaseOutputBuffer(outputBufferIndex, false);outputBufferIndex = mediacodec.dequeueOutputBuffer(bufferInfo, 10);}

二、MediaCodec编码视频

  • 申请录屏权限,获取MediaProjection对象

     /*** 开始直播* @param url 直播服务器地址* @param context 接收录屏请求回调的activity*/public void startLive(String url, Activity context){this.url = url;mediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);Intent intent = mediaProjectionManager.createScreenCaptureIntent();context.startActivityForResult(intent, 101);}/*** 屏幕录制权限申请后处理,返回结果,成功则开启屏幕推流,否则返回false* @param requestCode* @param resultCode* @param data* @return true 授权成功开启推流*         false 授权失败*/public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data){if (requestCode == 101 && resultCode == Activity.RESULT_OK){mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);LiveExecutors.getInstance().excute(this);return true;}else {return false;}}
  • 创建视频编码器

指定AVC格式(对应H264),宽高使用360*640,码率采用400_000(宽高和码率的设置为经验值,这里参考腾讯云实时音视频给出的参考值),帧率15fps,关键帧帧间间隔2s,本地原始视频格式采用MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface(录屏输出的格式);

  • 将编码器的surface传给MediaProjection对象构建虚拟显示

也就是让录屏画面通过编码器的surface进入编码器的输入队列;

MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,360, 640);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 400_000);//设置比特率mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//帧率mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//关键帧间隔mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);try {mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);mediaCodec.configure(mediaFormat,null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);Surface surface = mediaCodec.createInputSurface();//将编码器的surface传给mediaProjectionvirtualDisplay = mediaProjection.createVirtualDisplay("screen-vd",360, 640, 1,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,surface,null,null);start();//启动线程} catch (IOException e) {e.printStackTrace();}
  • 循环从编码器的输出队列读取数据

获得编码好的AVC格式的byte数组,留待后续rtmp封包使用。

    while (isEncoding){//编码器指定的关键帧间隔可能失效,所以需要手动2s刷新一下关键帧if (lastTimeTamp != 0){if (System.currentTimeMillis() - lastTimeTamp > 2000) {//每2秒强制刷新关键帧Bundle bundle = new Bundle();bundle.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);mediaCodec.setParameters(bundle);lastTimeTamp = System.currentTimeMillis();}} else {lastTimeTamp = System.currentTimeMillis();}//获取输出获取冲可用下标,timeoutUs是从队列中取数据的超时时间,这是个阻塞方法,如果超时则不继续等待int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10);if (index >= 0) {//获取输出缓冲区队列ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(index);byte[] bytes = new byte[bufferInfo.size];//从输出缓冲区队列读取数据byteBuffer.get(bytes);//获得了编码好的数据,封装出rmpt视频包//释放,让队列中index位置能放新数据mediaCodec.releaseOutputBuffer(index, false);}}

三、H264裸流格式分析

此时我们就获取到了AAC音频裸流和H264视频裸流。H264码流在网络中传输时实际是以NALU形式传输的,NALU就是NAL UNIT,NAL单元。NAL全称是NetWork Abstract Layer,即网络抽象层。我们此时得到的编码数据就是NAL数据。

一段包含了N个图像的H264裸数据,每个NAL之间由:

00 00 00 01 或者 00 00 01进行分割。

在分隔符之后的第一个字节就是表示这个NAL的类型:

0x67 : sps

0x68 : pps

0x65 : IDR首个关键帧

分析这些是因为,我们后续往RTMP包中封装的视频数据就是NAL数据,但是需要去掉每个NAL单元之间的分隔符,且根据NAL类型的不同,封装的数据格式也有区别。

为了更加形象的解释NAL单元我将获取到的AVC视频byte数组转换为16进制打印出来了,大家可以看一下。

四、FLV封装格式分析

FLV(Flash Video)是Adobe公司设计开发的一种流行的流媒体格式,由于其视频文件体积轻巧、封装简单等特点,使其很适合在互联网上进行应用。此外,FLV可以使用Flash Player进行播放,而Flash Player插件已经安装在全世界绝大部分浏览器上,这使得通过网页播放FLV视频十分容易。目前主流的视频网站如优酷网,土豆网,乐视网等网站无一例外地使用了FLV格式。FLV封装格式的文件后缀通常为“.flv”。

FLV文件总体格式:

FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag组成。因此一个FLV文件是如图结构。

我们主要关心Tag就可以,因为我们RTMP封包的数据就是由FLV中的Tag数据构成的。每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。下图展示了

FLV文件的详细结构。

我们主要就分析音频类型的Tag和视频类型的Tag,不涉及script data类型。两种类的header结构相同,所以下面主要分析他们的Tag data格式。

音频Tag Data格式:

音频Tag开始的第1个字节包含了音频数据的参数信息,从第2个字节开始为音频流数据即我们从编码器获取的音频流数据。

第一个字节参数信息分析:

前四位的音频编码类型如图所示:

2位的采样率的取值如图所示:

1位精度取值如图所示:

1位声道类型,单声道、双声道取值如下图所示,但是这里需要注意AAC格式的值总为1:

所以根据咱们上面代码中配置的编码格式,咱们最后的取值为10101111,转为16进制就是0xAF。

视频Tag Data格式分析:

下面这个图很重要,rtmp封包的时候里面的数据按照下面的格式封装。

注:图中sps[1]为profile sps[2]为兼容性 sps[3]为profile level

接下来用三张flv视频的格式分析图片来对照看看,我把AVC序列头类型的视频Tag分析写了一下,普通NAL单元类型视频和音频自己试试分析吧。

视频类型AVC序列头类型:

视频类型普通NAL单元类型:

音频类型:

五、利用librtmp封包发送

  • 利用librtmp连接流;
  • 根据数据类型封装分别封装视频类型和音频类型的package;
  • 利用librtmp发送package;

librtmp的工作流程:

首先我们Java层需要定义一个类RTMPPacket用来封装编码器读出的数据;

然后补全我们上面两种编码器中缺失的编码数据封装,

视频编码器中添加:

                //获得了编码好的数据,封装出rmpt视频包RTMPPackage rtmpPackage = new RTMPPackage();rtmpPackage.setBuffer(bytes);rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_VIDEO);if (firstTimeStamp == 0) {//记录第一次获取编码数据的时间firstTimeStamp = bufferInfo.presentationTimeUs/1000;}//tms需要的是一个相对时间rtmpPackage.setTms(bufferInfo.presentationTimeUs/1000 - firstTimeStamp);screenLive.addPackage(rtmpPackage);

音频编码器中添加:

需要注意的是在循环发送音频数据前,需要先发送一个音频数据的头部信息,

如果使用的是单声道就是 0x12 ,0x08,

双声道就是 0x12 ,0x10

        //rtmp中音频数据封包头部数据,只用发送一次RTMPPackage rtmpPackage = new RTMPPackage();rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_HEAD);rtmpPackage.setBuffer(new byte[]{0x12, 0x08});Log.d("RTMP", "AudioCodec addPackage");screenLive.addPackage(rtmpPackage);//rtmp音频封包rtmpPackage = new RTMPPackage();rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_DATA);rtmpPackage.setBuffer(outBuffer);if (firstTimeStamp == 0) {firstTimeStamp = bufferInfo.presentationTimeUs / 1000;}rtmpPackage.setTms(bufferInfo.presentationTimeUs / 1000 - firstTimeStamp);screenLive.addPackage(rtmpPackage);

接下来是JNI方法:

利用librtmp连接流:

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_mpass_xxapp_rtmp_ScreenLive_connect(JNIEnv *env, jobject thiz, jstring url_) {// TODO: implement connect()const char *url = env->GetStringUTFChars(url_, 0);int ret = 0;do{live = static_cast<Live *>(malloc(sizeof(Live)));memset(live,0, sizeof(Live));live->rtmp = RTMP_Alloc();//申请内存RTMP_Init(live->rtmp);//初始化//设置URLif (!(ret = RTMP_SetupURL(live->rtmp, const_cast<char *>(url)))) break;//开启输出模式RTMP_EnableWrite(live->rtmp);//连接服务器if (!(ret = RTMP_Connect(live->rtmp, 0))) break;//连接流if (!(ret = RTMP_ConnectStream(live->rtmp, 0))) break;LOGI("connect success");}while (0);if (!ret && live){free(live);live = NULL;}env->ReleaseStringUTFChars(url_, url);return ret;
}

音频封包:

RTMPPacket * buildAudioPackage(int8_t *buffer, int len, int type, long tms, RTMP *rtmp){RTMPPacket *audioPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));RTMPPacket_Alloc(audioPacket, len + 2);//申请内存audioPacket->m_body[0] = 0xAF;//设置音频参数,编码类型、采样率、精度、类型audioPacket->m_body[1] = 0x01;//非指定类型,不是头信息if (type == 1) {audioPacket->m_body[1] = 0x00;//头信息类型}memcpy(&audioPacket->m_body[2], buffer, len);//拷贝音频数据audioPacket->m_headerType = RTMP_PACKET_SIZE_LARGE;audioPacket->m_hasAbsTimestamp = 0;//是否使用绝对时间audioPacket->m_nTimeStamp = tms;//时间戳audioPacket->m_nBodySize = len + 2;//音频数据长度+头部的参数和类型2字节audioPacket->m_packetType = RTMP_PACKET_TYPE_AUDIO;audioPacket->m_nChannel = 0x05;audioPacket->m_nInfoField2 = rtmp->m_stream_id;return audioPacket;
}

视频封包:

1.判断视频类型,是否包含SPS和PPS信息:

int sendVideo(int8_t *byte, int len, long tms) {int ret;do{//视频数据的前四个字节是分隔符if (byte[4] == 0x67) {//如果是sps信息则需要保存到结构体中,随着下一次的AVC序列头发送if (live && (!live->pps || !live->sps)) {saveSequenceHeadMsg(byte, len, live);}} else{if (byte[4] == 0x65){//发送AVC序列头RTMPPacket *packet = buildVideoSequencePackage(live);if (!(ret = sendPackageData(packet))) {break;}}//发送普通NAL单元RTMPPacket *packet = buildVideoPackage(byte, len, tms, live->rtmp);ret = sendPackageData(packet);}}while (0);return ret;
}

2.获取SPS和PPS信息:

void saveSequenceHeadMsg(int8_t *buffer, int len, Live *live){for (int i = 0; i < len-4; ++i) {if (buffer[i] == 0x00 &&buffer[i+1] == 0x00 &&buffer[i+2] == 0x00 &&buffer[i+3] == 0x01) {//H264以NALU单元组成,每个单元之间以0x00 0x00 0x00 0x01分割if (buffer[i+4] == 0x68) {//67为sps,68为ppslive->sps_len = i-4;live->sps = static_cast<int8_t *>(malloc(sizeof(int8_t)));memcpy(live->sps, buffer+4, live->sps_len);live->pps_len = len-i-4;live->pps = static_cast<int8_t *>(malloc(sizeof(int8_t)));memcpy(live->pps, buffer+i+4, live->pps_len);LOGI("sps:%d pps:%d", live->sps_len, live->pps_len);break;}}}
}

3.封装AVC序列头信息:

RTMPPacket* buildVideoSequencePackage(Live *live){RTMPPacket *packet = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));//视频数据(0x17)1字节+类型(0x00序列头)1字节+合成时间3个字节全为0+版本(0x01)1字节+// 编码规格3字节(sps[1]+sps[2]+sps[3])+NALU长度1个字节(0xFF)+sps个数1个字节(0xE1)// +sps长度2个字节+sps内容+pps个数1个字节(0x01)+pps长度2个字节+pps内容int bodySize = 16+live->pps_len+live->sps_len;RTMPPacket_Alloc(packet, bodySize);int i=0;packet->m_body[i++] = 0x17;//数据类型 1关键帧 7AVCpacket->m_body[i++] = 0x00;//序列头类型packet->m_body[i++] = 0x00;//合成时间packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x01;//版本packet->m_body[i++] = live->sps[1];//编码规格sps[1]packet->m_body[i++] = live->sps[2];//编码规格sps[2]packet->m_body[i++] = live->sps[3];//编码规格sps[3]packet->m_body[i++] = 0xFF;//NALU长度packet->m_body[i++] = 0xE1;//sps个数packet->m_body[i++] = (live->sps_len>>8) & 0xff;//sps长度高位packet->m_body[i++] = live->sps_len & 0xff;//sps长度低位memcpy(&packet->m_body[i], live->sps, live->sps_len);//sps内容i += live->sps_len;packet->m_body[i++] = 0x01;//pps个数packet->m_body[i++] = (live->pps_len>>8) & 0xff;//pps长度高位packet->m_body[i++] = live->pps_len & 0xff;//pps长度低位memcpy(&packet->m_body[i], live->pps, live->pps_len);//pps内容packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;packet->m_nInfoField2 = live->rtmp->m_stream_id;packet->m_nChannel =0x04;packet->m_nBodySize = bodySize;packet->m_nTimeStamp = 0;packet->m_hasAbsTimestamp = 0;packet->m_headerType = RTMP_PACKET_SIZE_LARGE;return packet;
}

4.封装普通NAL单元信息:

RTMPPacket* buildVideoPackage(int8_t *buffer, int len, long tms, RTMP *rtmp){buffer += 4;//去掉分隔符len -= 4;RTMPPacket *videoPacket = static_cast<RTMPPacket *>(malloc(sizeof(RTMPPacket)));RTMPPacket_Alloc(videoPacket, len+9);//申请空间大小需要加上头部的9个字节videoPacket->m_body[0] = 0x27;if (buffer[0] == 0x65) {//关键帧videoPacket->m_body[0] = 0x17;LOGI("发送关键帧 data");}videoPacket->m_body[1] = 0x01;videoPacket->m_body[2] = 0x00;videoPacket->m_body[3] = 0x00;videoPacket->m_body[4] = 0x00;//长度videoPacket->m_body[5] = (len >> 24) & 0xff;videoPacket->m_body[6] = (len >> 16) & 0xff;videoPacket->m_body[7] = (len >> 8) & 0xff;videoPacket->m_body[8] = (len) & 0xff;memcpy(&videoPacket->m_body[9], buffer, len);videoPacket->m_packetType = RTMP_PACKET_TYPE_VIDEO;videoPacket->m_nInfoField2 = rtmp->m_stream_id;videoPacket->m_nChannel =0x04;videoPacket->m_nBodySize = len+9;videoPacket->m_nTimeStamp = tms;videoPacket->m_hasAbsTimestamp = 0;videoPacket->m_headerType = RTMP_PACKET_SIZE_LARGE;return videoPacket;
}

利用librtmp发包:

int sendPackageData(RTMPPacket *packet){int ret = RTMP_SendPacket(live->rtmp, packet, 1);//1代表加入发包队列RTMPPacket_Free(packet);free(packet);return ret;
}

好了基本思路就是这样的,至于写完如何验证程序正确性,你可以自己搭建Nginx服务器,也可以选择白嫖斗鱼的服务器,我用的后者,申请斗鱼主播,然后开播后会给你一个rtmp推流地址和推流码,拼接后就是librtmp需要的url地址,不过推流码有时间限制,最后看个推流成功的动态图结束吧。

参考文章链接:

lv格式详解+实例剖析

视音频编解码学习工程:FLV封装格式分析器

将h.264视频流封装成flv格式文件

H264参数SPS(序列参数集)和PPS(图像参数集)说明

Demo下载链接:

https://download.csdn.net/download/wozuihaole/12692722

android使用RTMP实现录屏直播推送音视频(已在享学公众号发表)相关推荐

  1. 如何在Android平台实现低延迟的RTMP/RTSP录屏直播

    许多开发者在做智慧教室同屏亦或会议同屏时,基于Andriod平台采集屏幕并编码推送,往往遇到各种各样的问题,以下就我们开发过程中的一些技术考量做个分享,权当抛砖引玉: 协议选择.数据来源和处理 1. ...

  2. Android之间互相的录屏直播 --点对点传输(tcp长连接发送h264)(一)

    前言 转载请注明出处 ,来自: 暂时两篇: (1) Android之间互相的录屏直播 –点对点传输(tcp长连接发送h264)(一) http://blog.csdn.net/baidu_335462 ...

  3. (一)android 桌面悬浮窗 录屏源码放送

    看到几个网友留言需要源码参考,需要的可以拿走: 这里先提供几个工具类: 1.录屏工具类ScreenUtil.java package com.android.systemui.util;import ...

  4. iOS rtmp 摄像头/录屏直播以及观看

    之前讲过如何在centos上使用nginx搭建rtmp服务器(链接),本文介绍一下iOS 端如何通过rtmp录屏直播以及观看,完整的工程代码地址(https://github.com/zxm006/R ...

  5. php主动推送弹幕_php简陋版实现微信公众号主动推送消息

    推荐一个网站 www.itziy.com csdn免积分下载器.pudn免积分下载器.51cto免积分下载器 www.verypan.com 百度网盘搜索引擎 www.94cto.com 编程相关视频 ...

  6. 如何在Android实现录屏直播

    许多开发者在做智慧教室同屏亦或会议同屏时,基于Andriod平台采集屏幕并编码推送,往往遇到各种各样的问题,以下就我们开发过程中的一些技术考量做个分享,权当抛砖引玉: 协议选择.数据来源和处理 1. ...

  7. android4.2屏幕录像,android——使用自带录屏工具进行屏幕录像

    在做开源项目的时候,想传一个gif效果图上去.但是,要有连贯的动画效果.所以,就想到先录制视频,然后视频转gif.但是,用第三录屏软件总是不完美. 那么,怎么办呢? android4.4 提供了自带录 ...

  8. android 桌面悬浮窗 录屏时间控制显示效果

    悬浮窗效果如上图所示: 很简单的一个布局直接上代码 悬浮窗是在service中拉起可以根据个人需要修改 代码: (一)android 桌面悬浮窗 录屏源码放送 (二)android 桌面悬浮窗 录屏源 ...

  9. EasyRTMP手机直播推送rtmp流flash无法正常播放问题

    本文转自EasyDarwin团队Kim的博客:http://blog.csdn.net/jinlong0603/article/details/52960750 问题简介 EasyRTMP是EasyD ...

最新文章

  1. 信息学奥赛一本通(C++)在线评测系统——基础(三)数据结构—— 1338:【例3-3】医院设置
  2. [转] sql server 跨数据库调用存储过程
  3. C++--Qt使用Http协议
  4. caffe,caffe2 and pytorch
  5. 使用webpack打包ThinkPHP的资源文件
  6. 大佬就是有想法!比尔盖茨办公室曝光:实体版元素周期表震撼!
  7. werkzeug response
  8. [hbase]Hbase 在HDFS上的目录树结构
  9. element ui el-carousel 滚动图 vue 基于vue-lazyload图片懒加载、延迟加载 解决方案
  10. 一元二次方程abc决定什么_情绪管理 - ABC理论
  11. 用国产编程语言CBrother做微信公众号后台开发太简单
  12. php劳保管理系统,《劳保用品管理系统》用盟威快速开发平台开发的应用实例
  13. 单片机的ISP是什么
  14. 电脑桌面显示计算机信息,在桌面背景图片上显示各种电脑信息BGInfo 4.28
  15. 玩转Qml(12)-再谈动态国际化
  16. Android Studio初学者实例:RecyclerView学习--模仿今日头条
  17. MTK6226-DS-PHB-SIMB-Load
  18. 中英文说明丨质膜H+ATP酶AS07 260介绍
  19. java写的网络版斗地主_用java实现斗地主
  20. 【存储数据恢复】HP EVA存储误删除VDISK的数据恢复案例

热门文章

  1. 傲世奇侠传5java_【图片】傲世奇侠传1-5全攻略_傲世奇侠传吧_百度贴吧
  2. html字体下沉怎么设置,css如何让字体下沉
  3. 设有6个有序表A、B、C、D、E、F,分别含有10、35、40、50、60和200个数据元素,各表中元素按升序排列。要求通过5次两两合并,将6个表最终合并成1个升序表,并在最坏情况下比较的总次数达到最
  4. 阿里云域名解析网络和服务架构设计(四) 之阿里云ECS服务器Nginx代理实践
  5. 湖畔大学之在湖边 ● 畅谈1号位的技术观
  6. 模块开发之React使用第三方库PropTypes属性限制(十二)
  7. 无向图G=(V,E)的二分图判断
  8. 材料力学研究的工程材料的基本假设是什么?均匀性假设与各向同性假设有何区别?...
  9. “启动修复”无法修复你的电脑解决方法
  10. VTK系列教程六:多平面重建