当前位置: 首页 > news >正文

NDK RTMP直播客户端二

在之前完成的实战项目【FFmpeg音视频播放器】属于拉流范畴,接下来将完成推流工作,通过RTMP实现推流,即直播客户端。简单的说,就是将手机采集的音频数据和视频数据,推到服务器端。

接下来的RTMP直播客户端系列,主要实现红框和紫色部分:

 本节主要内容:

​1.Java层视频编码工作。

2.Native层视频编码器工作。

3.Native层视频推流编码工作。

源码:

NdkPush: 通过RTMP实现推流,直播客户端。

一、Java层视频编码

1)MainActivity:

MainActivity只与中转站NdkPusher打交道,用户操作页面相关功能是调用NdkPusher分发下去;

初始化NdkPusher.java

mNdkPusher = new NdkPusher(this, Camera.CameraInfo.CAMERA_FACING_BACK, 640, 480, 25, 800000);

首次点击【切换摄像头】时,设置Camera与Surface绑定

/*** 切换摄像头** @param view*/
public void switchCamera(View view) {if (initPermission()) {if (!isBind) {mNdkPusher.setPreviewDisplay(mSurfaceHolder);isBind = true;}mNdkPusher.switchCamera();}
}

点击【开始直播】时,开始直播,并设置rtmp服务器地址

/*** 开始直播** @param view*/
public void startLive(View view) {mNdkPusher.startLive("rtmp://139.224.136.101/myapp");
}

点击【停止直播】时,停止直播

/*** 停止直播** @param view*/
public void stopLive(View view) {mNdkPusher.stopLive();
}

页面关闭,释放资源

/*** 释放工作*/
@Override
protected void onDestroy() {super.onDestroy();mNdkPusher.release();
}

2)NdkPusher:

中转站,分发MainActivity事件和和Native层打交道;

NdkPusher初始化时,主要是的三件事,

①:初始化native层需要的加载,
②:实例化视频通道并传递基本参数(宽高,fps,码率等),
③:实例化音频通道(下一节内容)

public NdkPusher(Activity activity, int cameraId, int width, int height, int fps, int bitrate) {native_init();// 将this传递给VideoChannel,方便VideoChannel操控native层mVideoChannel = new VideoChannel(this, activity, cameraId, width, height, fps, bitrate);
}

分发给视频通道VideoChannel-->SurfaceView与中转站里面的Camera绑定

public void setPreviewDisplay(SurfaceHolder surfaceHolder) {mVideoChannel.setPreviewDisplay(surfaceHolder);
}

分发给视频通道VideoChannel-->切换摄像头

public void switchCamera() {mVideoChannel.switchCamera();
}

开始直播,调用native层开始直播工作,分发给视频通道VideoChannel开始直播

public void startLive(String path) {native_start(path);mVideoChannel.startLive();
}

停止直播,调用native层停止直播工作,分发给视频通道VideoChannel停止直播

public void stopLive() {mVideoChannel.stopLive();native_stop();
}

释放工作,释放native层数据和视频通道VideoChannel

public void release() {mVideoChannel.release();native_release();
}

与native层通讯函数

// 音频 视频 公用的
private native void native_init(); // 初始化
private native void native_start(String path); // 开始直播start(音频视频通用一套代码) path:rtmp推流地址
private native void native_stop(); // 停止直播
private native void native_release(); // onDestroy--->release释放工作// 下面是视频独有
public native void native_initVideoEncoder(int width, int height, int mFps, int bitrate); // 初始化x264编码器
public native void native_pushVideo(byte[] data); // 相机画面的数据 byte[] 推给 native层

3)VideoChannel:

视频通道,处理NdkPusher分发下来的事件和将CameraHelper的Camera画面数据推送到native层。

初始化CameraHelper,设置Camera相机预览帮助类,onPreviewFrame(nv21)数据的回调监听和宽高发送改变的监听

public VideoChannel(NdkPusher ndkPusher, Activity activity, int cameraId, int width, int height, int fps, int bitrate) {this.mNdkPusher = ndkPusher; // 回调给中转站this.mFps = fps; // fps 每秒钟多少帧this.bitrate = bitrate; // 码率mCameraHelper = new CameraHelper(activity, cameraId, width, height);mCameraHelper.setPreviewCallback(this); // 设置Camera相机预览帮助类,onPreviewFrame(nv21)数据的回调监听mCameraHelper.setOnChangedSizeListener(this); // 宽高发送改变的监听回调设置
}

调用帮助类:与Surface绑定

public void setPreviewDisplay(SurfaceHolder surfaceHolder) {mCameraHelper.setPreviewDisplay(surfaceHolder);
}

调用帮助类-->切换摄像头

public void switchCamera() {mCameraHelper.switchCamera();
}

开始直播,只修改标记 让其可以进入if 完成图像数据推送

public void startLive() {isLive = true;
}

停止直播,只修改标记 让其可以不要进入if 就不会再数据推送了

public void stopLive() {isLive = false;
}

释放,调用帮助类-->停止预览

public void release() {mCameraHelper.stopPreview();
}

Camera预览画面的数据,回调到这里,再通过mNdkPusher,将数据推送到native层

@Override
public void onPreviewFrame(byte[] data, Camera camera) {// data == nv21 数据if (isLive) {// 图像数据推送mNdkPusher.native_pushVideo(data);}
}

Camera发送宽高改变,回调到这里,再通过mNdkPusher,将数据推送到native层

@Override
public void onChanged(int width, int height) {// 视频编码器的初始化有关:width,height,fps,bitratemNdkPusher.native_initVideoEncoder(width, height, mFps, bitrate); // 初始化x264编码器
}

4)CameraHelper第一节已完成。

二、Native层视频编码器

1)native-lib.cpp:

处理Java层NdkPusher调用的native函数;

native层初始化工作:

NdkPusher构造函数调用到这里,初始化native层VideoChannel,设置 Camera预览画面的数据推送到native层,videoChannel编码后数据,通过callback回调到native-lib.cpp,加入队列。

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1init(JNIEnv *env, jobject thiz) {// 初始化 VideoChannelvideoChannel = new VideoChannel();// 设置 Camera预览画面的数据推送到native层,videoChannel编码后数据,通过callback回调到native-lib.cpp,加入队列videoChannel->setVideoCallback(callback);// 设置 队列的释放工作 回调packets.setReleaseCallback(releasePackets);
}

videoCallback 函数指针的实现(将编码后数据存放packet到队列)

void callback(RTMPPacket *packet) {if (packet) {if (packet->m_nTimeStamp == -1) {packet->m_nTimeStamp = RTMP_GetTime() - start_time; // 如果是sps+pps 没有时间搓,如果是I帧就需要有时间搓}packets.push(packet); // 存入队列里面}
}

释放RTMPPacket * 包的函数指针实现,T无法释放, 让外界释放

void releasePackets(RTMPPacket **packet) {if (packet) {RTMPPacket_Free(*packet);delete packet;packet = nullptr;}
}

 初始化x264编码器,Camera宽高改变,回调到这里,首次设置预览时触发;分发到VideoChannel视频通道初始化编码器。

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1initVideoEncoder(JNIEnv *env, jobject thiz, jint width,jint height, jint fps, jint bitrate) {if (videoChannel) {videoChannel->initVideoEncoder(width, height, fps, bitrate);}
}

2)VideoChannel.cpp:

 native层视频通道,初始化x264编码器和处理相机原始数据编码,再回到给native-lib.cpp,加入队列。

初始化 x264 编码器

void VideoChannel::initVideoEncoder(int width, int height, int fps, int bitrate) {// 防止编码器多次创建 互斥锁pthread_mutex_lock(&mutex);mWidth = width;mHeight = height;mFps = fps;mBitrate = bitrate;y_len = width * height;uv_len = y_len / 4;// 防止重复初始化x264编码器if (videoEncoder) {x264_encoder_close(videoEncoder);videoEncoder = nullptr;}// 防止重复初始化pic_inif (pic_in) {x264_picture_clean(pic_in);DELETE(pic_in);}// TODO 初始化x264编码器x264_param_t param;// x264的参数集// 设置编码器属性// ultrafast 最快  (直播必须快)// zerolatency 零延迟(直播必须快)x264_param_default_preset(&param, "ultrafast", "zerolatency");// 编码规格:https://wikipedia.tw.wjbk.site/wiki/H.264 看图片param.i_level_idc = 32; // 3.2 中等偏上的规格  自动用 码率,模糊程度,分辨率// 输入数据格式是 YUV420P  平面模式VVVVVUUUU,如果没有P,  就是交错模式VUVUVUVUparam.i_csp = X264_CSP_I420;param.i_width = width;param.i_height = height;// 不能有B帧,如果有B帧会影响编码、解码效率(快)param.i_bframe = 0;// 码率控制方式。CQP(恒定质量),CRF(恒定码率),ABR(平均码率)param.rc.i_rc_method = X264_RC_CRF;// 设置码率param.rc.i_bitrate = bitrate / 1000;// 瞬时最大码率 网络波动导致的param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2;// 设置了i_vbv_max_bitrate就必须设置buffer大小,码率控制区大小,单位Kb/sparam.rc.i_vbv_buffer_size = bitrate / 1000;// 码率控制不是通过 timebase 和 timestamp,码率的控制,完全不用时间搓   ,而是通过 fps 来控制 码率(根据你的fps来自动控制)param.b_vfr_input = 0;// 分子 分母// 帧率分子param.i_fps_num = fps;// 帧率分母param.i_fps_den = 1;param.i_timebase_den = param.i_fps_num;param.i_timebase_num = param.i_fps_den;// 告诉人家,到底是什么时候,来一个I帧, 计算关键帧的距离// 帧距离(关键帧)  2s一个关键帧   (就是把两秒钟一个关键帧告诉人家)param.i_keyint_max = fps * 2;// sps序列参数   pps图像参数集,所以需要设置header(sps pps)// 是否复制sps和pps放在每个关键帧的前面 该参数设置是让每个关键帧(I帧)都附带sps/pps。param.b_repeat_headers = 1;// 并行编码线程数param.i_threads = 1;// profile级别,baseline级别 (把我们上面的参数进行提交)x264_param_apply_profile(&param, "baseline");// 输入图像初始化pic_in = new x264_picture_t(); // 本身空间的初始化x264_picture_alloc(pic_in, param.i_csp, param.i_width, param.i_height); // pic_in内部成员初始化等// 打开编码器 一旦打开成功,我们的编码器就拿到了videoEncoder = x264_encoder_open(&param);if (videoEncoder) {LOGE("x264编码器打开成功");}pthread_mutex_unlock(&mutex);
}

三、Native层视频推流编码

1)native-lib.cpp:

开始直播 ---> 启动工作

创建子线程实现:
1.连接流媒体服务器;
2.发包;

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1start(JNIEnv *env, jobject thiz, jstring path_) {/*** 创建子线程:* 1.连接流媒体服务器;* 2.发包;*/if (isStart) {return;}isStart = true;const char *path = env->GetStringUTFChars(path_, nullptr);// 深拷贝char *url = new char(strlen(path) + 1); // C++的堆区开辟 new -- deletestrcpy(url, path);// 创建线程来进行直播pthread_create(&pid_start, nullptr, task_start, url);env->ReleaseStringUTFChars(path_, path); // 你随意释放,我已经深拷贝了
}

连接RTMP服务器,遍历压缩包队列,将数据发送到RTMP服务器

void *task_start(void *args) {char *url = static_cast<char *>(args);// RTMPDump API 九部曲RTMP *rtmp = nullptr;int result; // 返回值判断成功失败do {// 1.1,rtmp 初始化rtmp = RTMP_Alloc();if (!rtmp) {LOGE("rtmp 初始化失败");break;}// 1.2,rtmp 初始化RTMP_Init(rtmp);rtmp->Link.timeout = 5; // 设置连接的超时时间(以秒为单位的连接超时)// 2,rtmp 设置流媒体地址result = RTMP_SetupURL(rtmp, url);if (!result) { // result == 0 和 ffmpeg不同,0代表失败LOGE("rtmp 设置流媒体地址失败");break;}// 3,开启输出模式RTMP_EnableWrite(rtmp);// 4,建立连接result = RTMP_Connect(rtmp, nullptr);if (!result) { // result == 0 和 ffmpeg不同,0代表失败LOGE("rtmp 建立连接失败:%d, url: %s", result, url);break;}// 5,连接流result = RTMP_ConnectStream(rtmp, 0);if (!result) { // result == 0 和 ffmpeg不同,0代表失败LOGE("rtmp 连接流失败");break;}start_time = RTMP_GetTime();// 准备好了,可以开始向服务器推流了readyPushing = true;// 队列开始工作packets.setWork(1);RTMPPacket *packet = nullptr;// 从队列里面获取压缩包,直接发给服务器while (readyPushing) {packets.pop(packet); // 阻塞式if (!readyPushing) {break;}// 取不到数据,重新取,可能还没生产出来if (!packet) {continue;}// 到这里就是成功的获取队列的ptk了,可以发送给流媒体服务器packet->m_nInfoField2 = rtmp->m_stream_id;// 给rtmp的流id// 成功取出数据包,发送result = RTMP_SendPacket(rtmp, packet, 1); // 1==true 开启内部缓冲// packet 你都发给服务器了,可以大胆释放releasePackets(&packet);if (!result) { // result == 0 和 ffmpeg不同,0代表失败LOGE("rtmp 失败 自动断开服务器");break;}}releasePackets(&packet); // 只要跳出循环,就释放} while (false);// 本次一系列释放工作isStart = false;readyPushing = false;packets.setWork(0);packets.clear();if (rtmp) {RTMP_Close(rtmp);RTMP_Free(rtmp);}delete url;return nullptr;
}

Camera预览画面的数据,回调到这里,将原始数据进行x264编码后,得到的RTMPPkt(压缩数据)加入队列里面

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_push_NdkPusher_native_1pushVideo(JNIEnv *env, jobject thiz, jbyteArray data_) {if (!videoChannel || !readyPushing) { return; }// 把jni ---> C语言的jbyte *data = env->GetByteArrayElements(data_, nullptr);// data == nv21数据,编码,加入队列videoChannel->encodeData(data);env->ReleaseByteArrayElements(data_, data, 0); // 释放byte[]
}

2)VideoChannel.cpp:

视频原始数据编码工作

void VideoChannel::encodeData(signed char *data) {pthread_mutex_lock(&mutex);// 把nv21的y分量 Copy i420的y分量memcpy(pic_in->img.plane[0], data, y_len);// 把nv21的vuvuvuvu 转化成 i420的 uuuuvvvvfor (int i = 0; i < uv_len; ++i) {// u 数据// data + y_len + i * 2 + 1 : 移动指针取 data(nv21) 中 u 的数据*(pic_in->img.plane[1] + i) = *(data + y_len + i * 2 + 1);// v 数据// data + y_len + i * 2 : 移动指针取 data(nv21) 中 v 的数据*(pic_in->img.plane[2] + i) = *(data + y_len + i * 2);}x264_nal_t *nal = nullptr; // 通过H.264编码得到NAL数组(理解)int pi_nal; // pi_nal是nal中输出的NAL单元的数量x264_picture_t pic_out; // 输出编码后图片 (编码后的图片)// 1.视频编码器, 2.nal,  3.pi_nal是nal中输出的NAL单元的数量, 4.输入原始的图片,  5.输出编码后图片int ret = x264_encoder_encode(videoEncoder, &nal, &pi_nal, pic_in,&pic_out); // 进行编码(本质的理解是:编码一张图片)if (ret < 0) { // 返回值:x264_encoder_encode函数 返回返回的 NAL 中的字节数。如果没有返回 NAL 单元,则在错误时返回负数和零。LOGE("x264编码失败");pthread_mutex_unlock(&mutex); // 注意:一旦编码失败了,一定要解锁,否则有概率性造成死锁了return;}// 发送 Packets 入队queue// sps(序列参数集) pps(图像参数集) 说白了就是:告诉我们如何解码图像数据int sps_len, pps_len; // sps 和 pps 的长度uint8_t sps[100]; // 用于接收 sps 的数组定义uint8_t pps[100]; // 用于接收 pps 的数组定义pic_in->i_pts += 1; // pts显示的时间(+=1 目的是每次都累加下去), dts编码的时间// 遍历nal中输出的NAL单元,组件压缩包数据,加入队列for (int i = 0; i < pi_nal; ++i) {if (nal[i].i_type == NAL_SPS) {sps_len = nal[i].i_payload - 4; // 去掉起始码(之前我们学过的内容:00 00 00 01)memcpy(sps, nal[i].p_payload + 4, sps_len); // 由于上面减了4,所以+4挪动这里的位置开始} else if (nal[i].i_type == NAL_PPS) {pps_len = nal[i].i_payload - 4; // 去掉起始码 之前我们学过的内容:00 00 00 01)memcpy(pps, nal[i].p_payload + 4, pps_len); // 由于上面减了4,所以+4挪动这里的位置开始// sps + pps == 1个压缩包数据sendSpsPps(sps, pps, sps_len, pps_len); // pps是跟在sps后面的,这里拿到的pps表示前面的sps肯定拿到了} else {// 发送 I帧 P帧sendFrame(nal[i].i_type, nal[i].i_payload, nal[i].p_payload);}}
}

组装sps + pps == 1个压缩包数据,存入队列

void VideoChannel::sendSpsPps(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {// 根据协议设置压缩包数据长度int body_size = 5 + 8 + sps_len + 3 + pps_len;RTMPPacket *packet = new RTMPPacket; // 开始封包RTMPPacketRTMPPacket_Alloc(packet, body_size); // 堆区实例化 RTMPPacketint i = 0;packet->m_body[i++] = 0x17; // 十六进制转换成二进制,二进制查表 就懂了packet->m_body[i++] = 0x00;   // 重点是此字节 如果是1 帧类型(关键帧 非关键帧), 如果是0一定是 sps ppspacket->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;packet->m_body[i++] = 0x00;// 看图说话packet->m_body[i++] = 0x01; // 版本packet->m_body[i++] = sps[1];packet->m_body[i++] = sps[2];packet->m_body[i++] = sps[3];packet->m_body[i++] = 0xFF;packet->m_body[i++] = 0xE1;// 两个字节表达一个长度,需要位移// 用两个字节来表达 sps的长度,所以就需要位运算,取出sps_len高8位 再取出sps_len低8位//(位运算:https://blog.csdn.net/qq_31622345/article/details/98070787)// https://www.cnblogs.com/zhu520/p/8143688.htmlpacket->m_body[i++] = (sps_len >> 8) & 0xFF; // 取高8位packet->m_body[i++] = sps_len & 0xFF; // 去低8位memcpy(&packet->m_body[i], sps, sps_len); // sps拷贝进去了i += sps_len; // 拷贝完sps数据 ,i移位,(下面才能准确移位)packet->m_body[i++] = 0x01; // pps个数,用一个字节表示packet->m_body[i++] = (pps_len >> 8) & 0xFF; // 取高8位packet->m_body[i++] = pps_len & 0xFF; // 去低8位memcpy(&packet->m_body[i], pps, pps_len); // pps拷贝进去了i += pps_len; // 拷贝完pps数据 ,i移位,(下面才能准确移位)// 封包处理packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 包类型 视频包packet->m_nBodySize = body_size; // 设置好 sps+pps的总大小packet->m_nChannel = 10; // 通道ID,随便写一个,注意:不要写的和rtmp.c(里面的m_nChannel有冲突 4301行)packet->m_nTimeStamp = 0; // sps pps 包 没有时间戳packet->m_hasAbsTimestamp = 0; // 时间戳绝对或相对 也没有时间搓packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; // 包的类型:数据量比较少,不像帧数据(那就很大了),所以设置中等大小的包// packet 存入队列videoCallback(packet);
}

发送帧信息,把帧类型 RTMPPacket 存入队列

void VideoChannel::sendFrame(int type, int payload, uint8_t *pPayload) {// 去掉起始码 00 00 00 01 或者 00 00 01if (pPayload[2] == 0x00){ // 00 00 00 01pPayload += 4; // 例如:共10个,挪动4个后,还剩6个// 保证 我们的长度是和上的数据对应,也要是6个,所以-= 4payload -= 4;}else if(pPayload[2] == 0x01){ // 00 00 01pPayload +=3; // 例如:共10个,挪动3个后,还剩7个// 保证 我们的长度是和上的数据对应,也要是7个,所以-= 3payload -= 3;}// 根据协议设置压缩包数据长度int body_size = 5 + 4 + payload;RTMPPacket *packet = new RTMPPacket; // 开始封包RTMPPacketRTMPPacket_Alloc(packet, body_size); // 堆区实例化 RTMPPacket// 区分关键帧 和 非关键帧packet->m_body[0] = 0x27; // 普通帧 非关键帧if(type == NAL_SLICE_IDR){packet->m_body[0] = 0x17; // 关键帧}packet->m_body[1] = 0x01; // 重点是此字节 如果是1 帧类型(关键帧或非关键帧), 如果是0一定是 sps ppspacket->m_body[2] = 0x00;packet->m_body[3] = 0x00;packet->m_body[4] = 0x00;// 四个字节表达一个长度,需要位移// 用四个字节来表达 payload帧数据的长度,所以就需要位运算//(位运算:https://blog.csdn.net/qq_31622345/article/details/98070787)// https://www.cnblogs.com/zhu520/p/8143688.htmlpacket->m_body[5] = (payload >> 24) & 0xFF;packet->m_body[6] = (payload >> 16) & 0xFF;packet->m_body[7] = (payload >> 8) & 0xFF;packet->m_body[8] = payload & 0xFF;memcpy(&packet->m_body[9], pPayload, payload); // 拷贝H264的裸数据packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 包类型,是视频类型packet->m_nBodySize = body_size; // 设置好 关键帧 或 普通帧 的总大小packet->m_nChannel = 10; // 通道ID,随便写一个,注意:不要写的和rtmp.c(里面的m_nChannel有冲突 4301行)packet->m_nTimeStamp = -1; // 帧数据有时间戳packet->m_hasAbsTimestamp = 0; // 时间戳绝对或相对 用不到,不需要packet->m_headerType = RTMP_PACKET_SIZE_LARGE ; // 包的类型:若是关键帧的话,数据量比较大,所以设置大包// 把最终的 帧类型 RTMPPacket 存入队列videoCallback(packet);
}

当压缩数据加入队列后,开启直播创建的子线程将会获取队列的压缩数据,发送到RTMP服务器。

源码:

NdkPush: 通过RTMP实现推流,直播客户端。

视频推流完成,下一节开始音频推流工作。。。

相关文章:

NDK RTMP直播客户端二

在之前完成的实战项目【FFmpeg音视频播放器】属于拉流范畴&#xff0c;接下来将完成推流工作&#xff0c;通过RTMP实现推流&#xff0c;即直播客户端。简单的说&#xff0c;就是将手机采集的音频数据和视频数据&#xff0c;推到服务器端。 接下来的RTMP直播客户端系列&#xff…...

Python3--垃圾回收机制

一、概述 Python 内部采用 引用计数法&#xff0c;为每个对象维护引用次数&#xff0c;并据此回收不在需要的垃圾对象。由于引用计数法存在重大缺陷&#xff0c;循环引用时由内存泄露风险&#xff0c;因此Python还采用 标记清除法 来回收在循环引用的垃圾对象。此外&#xff0c…...

C/C++开发,认识opencv各模块

目录 一、opencv模块总述 二、opencv主要模块 2.1 opencv安装路径及内容 2.2 opencv模块头文件说明 2.3 成熟OpenCV主要模块 2.4 社区支持的opencv_contrib扩展主要模块 2.5 关于库文件的引用 一、opencv模块总述 opencv的主要能力在于图像处理&#xff0c;尤其是针对二维图…...

【WLSM、FDM状态估计】电力系统状态估计研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

准备2023(2024)蓝桥杯

前缀和 一维前缀和 s[i]s[i-1]a[i]二维前缀和&#xff08;子矩阵的和&#xff09; s[i][j]s[i-1][j]s[i][j-1]-s[i-1][j-1]a[i][j] 差分 一维数组 //b是差分数组b[i]c;b[j1]-c;例题 #include<iostream> using namespace std; int n,m; int b[100002],a[100002]; vo…...

剑指 Offer 60. n个骰子的点数

剑指 Offer 60. n个骰子的点数 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 把n个骰子扔在地上&#xff0c;所有骰子朝上一面的点数之和为s。输入n&#xff0c;打印出s的所有可能的值出现的概率。 你需要用一个浮点数数组返回答案&#xff0c;其中第 i 个…...

阿里巴巴-淘宝搜索排序算法学习

模型效能&#xff1a;模型结构优化 模型效能&#xff1a;减枝 FLOPS&#xff1a;每秒浮点运算的次数 模型效能&#xff1a;量化 基于统计阈值限定&#xff0c;基于学习阈值限定。 平台效能&#xff1a;一站式DL训练平台 平台效能&#xff1a;搜索模型的系统流程 协同关系…...

〖Python网络爬虫实战⑮〗- pyquery的使用

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付费…...

SQL综合查询下

SQL综合查询下 目录SQL综合查询下18、查询所有人都选修了的课程号与课程名题目代码题解19、SQL查询&#xff1a;查询没有参加选课的学生。题目代码20、SQL查询&#xff1a;统计各门课程选修人数&#xff0c;要求输出课程代号&#xff0c;课程名&#xff0c;有成绩人数&#xff…...

全连接层FC

lenet结构: 输入层(Input Layer):接收手写数字的图像数据,通常是28x28的灰度图像。 卷积层1(Convolutional Layer 1):对输入图像进行卷积操作,提取低级别的特征,使用 6 个大小为 5x5 的卷积核进行卷积,得到 6 个输出特征图,激活函数为 Sigmoid。 平均池化层1(Aver…...

图的遍历及连通性

文章目录 图的遍历及连通性程序设计程序分析图的遍历及连通性 【问题描述】 根据输入的图的邻接矩阵A,判断此图的连通分量的个数。 【输入形式】 第一行为图的结点个数n,之后的n行为邻接矩阵的内容,每行n个数表示。其中A[i][j]=1表示两个结点邻接,而A[i][j]=0表示两个结点无…...

DJ3-4 实时调度

目录 3.4.1 实现实时调度的基本条件 1. 提供必要的信息 2. 系统的处理能力强 3. 采用抢占式调度机制 4. 具有快速切换机制 3.4.2 实时调度算法的分类 1. 非抢占式调度算法 2. 抢占式调度算法 3.4.3 常用的几种实时调度算法 1. 最早截止时间优先 EDF&#xff08;Ea…...

Oracle之PL/SQL游标练习题(三)

游标练习题目1、定义游标&#xff1a;列出每个员工的姓名部门名称并编程显示第10个到第20个记录2、定义游标&#xff1a;从雇员表中显示工资大于3000的记录&#xff0c;只要姓名、部门编号和工资&#xff0c;编程显示其中的奇数记录3、用游标显示所有部门编号与名称&#xff0c…...

docker运行服务端性能监控系统Prometheus和数据分析系统Grafana

文章目录一、Prometheus的安装和运行1、使用docker拉取镜像2、创建prometheus.yml文件3、启动容器4、查看启动是否成功5、记录安装过程中出现的错误二、Grafana的安装和运行1、使用docker拉取镜像2、创建grafana3、运行grafana4、查看grafana运行日志5、登录grafana一、Prometh…...

【Linux】【应用层】多线程编程

一、线程创建 Linux 中的 pthread_create() 函数用来创建线程&#xff0c;它声明在<pthread.h>头文件中&#xff0c;语法格式如下&#xff1a; int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);各个参数…...

GameFramework 框架详解之 如何接入热更框架HybridCLR

一.前言 HybridCLR是一个特性完整、零成本、高性能、低内存的近乎完美的c#热更新方案 GameFramework是一个非常出色完整的基于Unity引擎的游戏框架,里面包含了非常多的模块,封装非常完整。 以前市面上的热更大多数都是Lua为主,后来出了一个ILRuntime的C#热更框架,虽然性能…...

全国青少年软件编程(Scratch)等级考试二级考试真题2023年3月——持续更新.....

一、单选题(共25题,共50分) 1. 小猫的程序如图所示,积木块的颜色与球的颜色一致。点击绿旗执行程序后,下列说法正确的是?( ) A.小猫一直在左右移动,嘴里一直说着“抓到了”。 B.小猫会碰到球,然后停止。 C.小猫一直在左右移动,嘴里一直说着“别跑” D.小猫会碰到球,…...

HTML2.1列表标签

列表标签种类 无序列表 有序列表 自定义列表 使用场景&#xff1a;在列表中按照行展示关联性内容。 特点&#xff1a;按照行的形式&#xff0c;整齐显示内容。 一、无序列表 标签名说明ul无序列表整体&#xff0c;用于包裹li标签li表示无序列表的每一项&#xff0c;用于包…...

在 Flutter 多人视频通话中实现虚拟背景、美颜与空间音效

前言 在之前的「基于声网 Flutter SDK 实现多人视频通话」里&#xff0c;我们通过 Flutter 声网 SDK 完美实现了跨平台和多人视频通话的效果&#xff0c;那么本篇我们将在之前例子的基础上进阶介绍一些常用的特效功能&#xff0c;包括虚拟背景、色彩增强、空间音频、基础变声…...

Ambari-web 架构

Ambari-web 使用的前端 Embar.js MVC 框架实现&#xff0c;Embar.js 是一个 TodoMVC 框架&#xff0c;涵盖了单页面应用&#xff08;single page application&#xff09;几乎所有的行为 Nodejs 是一个基于 Chrome JavaScript 运行时建立的一个平台&#xff0c;用来方便的搭建…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...