RTMP推流H264和AAC

使用 librtmp 库实现推流h264和aac文件,rtmp服务器使用SRS搭建,拉流端使用VLC。其中用到的h264和aac文件解析部分代码在我其它博客中有写:C/C++ AAC文件解析-CSDN博客、C/C++ H264文件解析-CSDN博客。

推流部分源码(C++)如下:

rtmp_ah_publish.h:

#ifndef _RTMP_AH_PUBLISH_H_
#define _RTMP_AH_PUBLISH_H_#include <string>
#include "librtmp/rtmp.h"class Rtmp_ah_publish
{
private:RTMP *rtmp;uint8_t *dataBuffer;uint32_t bufSize;public:Rtmp_ah_publish(size_t size = 1024 * 1024);~Rtmp_ah_publish();// 连接rtmp服务器int connect(const std::string url);// 断开连接void close();/*** @brief 发送AAC序列头** @param profile      AAC规格* @param freq_index   AAC采样频率索引* @param chan_config  AAC声道配置** @return 成功返回 0,失败返回 -1*/int send_aac_sequence_header(uint8_t profile, uint8_t freq_index, uint8_t chan_config);/*** @brief 发送AAC数据** @param data   一帧AAC数据,不含ADTS头* @param len    数据长度* @param dts_ms 时间戳** @return 成功返回 0,失败返回 -1*/int send_aacData(const uint8_t *data, uint32_t len, uint32_t dts_ms);/*** @brief 发送H264序列头** @param sps    sps数据,不含起始码* @param spslen sps数据长度* @param pps    pps数据,不含起始码* @param ppslen pps数据长度** @return 成功返回 0,失败返回 -1*/int send_avc_sequence_header(const uint8_t *sps, uint32_t spslen,const uint8_t *pps, uint32_t ppslen);/*** @brief 发送H264数据** @param data   一个NALU数据,不含起始码* @param len    数据长度* @param dts_ms 时间戳** @return 成功返回 0,失败返回 -1,丢弃该NALU返回 -2*/int send_h264Data(const uint8_t *data, uint32_t len, uint32_t dts_ms);
};#endif // _RTMP_AH_PUBLISH_H_

rtmp_ah_publish.cpp:

#include "rtmp_ah_publish.h"
#include <iostream>
#include <cstring>Rtmp_ah_publish::Rtmp_ah_publish(size_t size)
{dataBuffer = nullptr;bufSize = 0;try{dataBuffer = new uint8_t[size];}catch (const std::bad_alloc &e){std::cout << "Memory allocation failed: " << e.what() << std::endl;}bufSize = size;
}Rtmp_ah_publish::~Rtmp_ah_publish()
{delete[] dataBuffer;dataBuffer = nullptr;bufSize = 0;
}int Rtmp_ah_publish::connect(const std::string url)
{rtmp = nullptr;rtmp = RTMP_Alloc();if (!rtmp){std::cout << "RTMP_Alloc failed" << std::endl;return -1;}RTMP_Init(rtmp);rtmp->Link.timeout = 10; // 10秒超时// rtmp->Link.lFlags |= RTMP_LF_LIVE; // 实时流标识if (!RTMP_SetupURL(rtmp, (char *)url.c_str())){std::cout << "RTMP_SetupURL failed" << std::endl;goto err;}// 设置为推流模式RTMP_EnableWrite(rtmp);// 建立连接if (!RTMP_Connect(rtmp, NULL)){std::cout << "RTMP_Connect failed" << std::endl;goto err;}// 创建流if (!RTMP_ConnectStream(rtmp, 0)){std::cout << "RTMP_ConnectStream failed" << std::endl;goto err;}return 0;err:if (rtmp){RTMP_Close(rtmp);RTMP_Free(rtmp);}return -1;
}void Rtmp_ah_publish::close()
{RTMP_Close(rtmp);RTMP_Free(rtmp);rtmp = nullptr;
}int Rtmp_ah_publish::send_aac_sequence_header(uint8_t profile, uint8_t freq_index, uint8_t chan_config)
{int ret = -1;// 组装 AudioSpecificConfig(2 字节)// AudioSpecificConfig 格式:// - 5 位:音频对象类型(Audio Object Type)// - 4 位:采样频率索引// - 4 位:声道配置uint8_t asc[2] = {0};asc[0] = ((profile + 1) << 3) | (freq_index >> 1);asc[1] = ((freq_index & 0x1) << 7) | (chan_config << 3);// 创建 FLV Audio Tag Data// FLV Audio Tag Data 结构:// - 1 字节:[SoundFormat][SoundRate][SoundSize][SoundType]// - 1 字节:AACPacketType// - n 字节:AudioSpecificConfiguint8_t audio_tag[4] = {0};// 配置第一个字节:// - 4位 SoundFormat = 10(AAC)// - 2位 SoundRate  = 3(44kHz,对于AAC总是3)// - 1位 SoundSize = 1(16 位,对于AAC总是1)// - 1位 SoundType = 1(立体声,对于AAC总是1)audio_tag[0] = (10 << 4) | (3 << 2) | (1 << 1) | 1;// printf("audio_tag[0]: 0x%02X\n", audio_tag[0]);audio_tag[1] = 0; // AACPacketType = 0(序列头)audio_tag[2] = asc[0];audio_tag[3] = asc[1];RTMPPacket packet;RTMPPacket_Reset(&packet);RTMPPacket_Alloc(&packet, sizeof(audio_tag)); // 给 packet.m_body 分配空间if (!packet.m_body){std::cout << "RTMPPacket_Alloc failed" << std::endl;return -1;}memcpy(packet.m_body, audio_tag, sizeof(audio_tag));packet.m_nBodySize = sizeof(audio_tag);packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;packet.m_nChannel = 0x04;     // 通道ID,音频或视频为0x04packet.m_nTimeStamp = 0;      // 序列头的时间戳为 0packet.m_hasAbsTimestamp = 0; // 0: 相对时间戳 1: 绝对时间戳packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;packet.m_nInfoField2 = rtmp->m_stream_id;// 检查连接的状态if (!RTMP_IsConnected(rtmp)){printf("rtmp disconnect!\n");goto end;}// 发送,0表示直接发送,1表示放进发送队列if (!RTMP_SendPacket(rtmp, &packet, 1)){std::cout << "RTMP_SendPacket failed" << std::endl;goto end;}ret = 0;end:RTMPPacket_Free(&packet);return ret;
}int Rtmp_ah_publish::send_aacData(const uint8_t *data, uint32_t len, uint32_t dts_ms)
{if (len + 2 > bufSize - RTMP_MAX_HEADER_SIZE){std::cout << "error: exceeds buffer size." << std::endl;return -1;}// 创建 FLV Audio Tag Datauint8_t audio_tag[2] = {0};audio_tag[0] = (10 << 4) | (3 << 2) | (1 << 1) | 1;audio_tag[1] = 1; // AACPacketType = 1(aac原始数据)RTMPPacket packet;RTMPPacket_Reset(&packet);// 根据 RTMPPacket_Alloc 函数内部实现,m_body 是指向RTMP头后的内存packet.m_body = (char *)dataBuffer + RTMP_MAX_HEADER_SIZE;packet.m_nBodySize = len + 2;packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;packet.m_nChannel = 0x04;packet.m_nTimeStamp = dts_ms;packet.m_hasAbsTimestamp = 0;packet.m_headerType = RTMP_PACKET_SIZE_LARGE;packet.m_nInfoField2 = rtmp->m_stream_id;memcpy(packet.m_body, audio_tag, 2);memcpy(packet.m_body + 2, data, len);// 检查连接的状态if (!RTMP_IsConnected(rtmp)){printf("rtmp disconnect!\n");return -1;}// 发送,0表示直接发送,1表示放进发送队列if (!RTMP_SendPacket(rtmp, &packet, 1)){std::cout << "RTMP_SendPacket failed" << std::endl;return -1;}return 0;
}int Rtmp_ah_publish::send_avc_sequence_header(const uint8_t *sps, uint32_t spslen,const uint8_t *pps, uint32_t ppslen)
{int ret = -1;// 构建 AVCDecoderConfigurationRecord// 参考 ISO/IEC 14496-15uint32_t avc_config_size = 11 + spslen + ppslen;uint8_t avc_config[avc_config_size] = {0};uint8_t *p = avc_config;// configurationVersion (版本号,通常为1)*p++ = 0x01;// AVCProfileIndication*p++ = sps[1];// profile_compatibility*p++ = sps[2];// AVCLevelIndication*p++ = sps[3];// reserved (6 bits,保留字节,都为1) +// lengthSizeMinusOne (2 bits,表示在视频流中每个NAL单元长度字段的实际字节数减一,// 该字段值一般为3)*p++ = 0xFF;// reserved (3 bits,保留字节,都为1) +// numOfSequenceParameterSets (5 bits,SPS个数,一般为1)*p++ = 0xE1;// sequenceParameterSetLength (SPS长度)*p++ = (spslen >> 8) & 0xFF;*p++ = spslen & 0xFF;// sequenceParameterSetNALUnit (SPS数据)memcpy(p, sps, spslen);p += spslen;// numOfPictureParameterSets (PPS的个数)*p++ = 0x01;// pictureParameterSetLength (PPS长度)*p++ = (ppslen >> 8) & 0xFF;*p++ = ppslen & 0xFF;// pictureParameterSetNALUnit (PPS数据)memcpy(p, pps, ppslen);// 构建 AVCVIDEOPACKETuint32_t avcvideopacket_size = 4 + avc_config_size;uint8_t avcvideopacket[avcvideopacket_size] = {0};// AVCPacketType = 0 (序列头)avcvideopacket[0] = 0x00;// CompositionTime = 0 (PTS 和 DTS 之间的差值,PTS = DTS + CompositionTime)avcvideopacket[1] = 0x00;avcvideopacket[2] = 0x00;avcvideopacket[3] = 0x00;// AVCDecoderConfigurationRecordmemcpy(avcvideopacket + 4, avc_config, avc_config_size);// 创建 FLV Video Tag Data// FLV Video Tag Data 结构:// - 1 字节:[FrameType][CodecID]// - n 字节:AVCVIDEOPACKETuint32_t video_tag_size = 1 + avcvideopacket_size;uint8_t video_tag[video_tag_size] = {0};// FrameType = 1 (关键帧), CodecID = 7 (AVC), 各占 4 bitsvideo_tag[0] = 0x17;memcpy(video_tag + 1, avcvideopacket, avcvideopacket_size);// 创建 RTMPPacketRTMPPacket packet;RTMPPacket_Reset(&packet);RTMPPacket_Alloc(&packet, video_tag_size);if (!packet.m_body){std::cout << "RTMPPacket_Alloc failed" << std::endl;return -1;}memcpy(packet.m_body, video_tag, video_tag_size);packet.m_nBodySize = video_tag_size;packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;packet.m_nChannel = 0x04; // 通道ID,音频或视频为0x04packet.m_nTimeStamp = 0;  // 序列头的时间戳为 0packet.m_hasAbsTimestamp = 0;packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;packet.m_nInfoField2 = rtmp->m_stream_id;// 检查连接的状态if (!RTMP_IsConnected(rtmp)){printf("rtmp disconnect!\n");goto end;}// 发送,0表示直接发送,1表示放进发送队列if (!RTMP_SendPacket(rtmp, &packet, 1)){std::cout << "RTMP_SendPacket failed" << std::endl;goto end;}ret = 0;end:RTMPPacket_Free(&packet);return ret;
}int Rtmp_ah_publish::send_h264Data(const uint8_t *data, uint32_t len, uint32_t dts_ms)
{if (len + 9 > bufSize - RTMP_MAX_HEADER_SIZE){std::cout << "error: exceeds buffer size." << std::endl;return -1;}// 创建 FLV Video Tag Datauint8_t video_tag[9] = {0};uint8_t nal_type = data[0] & 0x1f; // 帧类型// FrameType = 1 (关键帧) 或 2 (非关键帧), CodecID = 7 (AVC)switch (nal_type){case 0x01: // 非关键帧video_tag[0] = 0x27;break;case 0x05: // 关键帧video_tag[0] = 0x17;break;default:// std::cout << "warn: Discard nal, nal type: " << static_cast<uint16_t>(nal_type) << std::endl;return -2;}// AVCPacketType = 1 (AVC NALU)video_tag[1] = 0x01;// CompositionTime = 0video_tag[2] = 0x00;video_tag[3] = 0x00;video_tag[4] = 0x00;// Nalu lenvideo_tag[5] = (len >> 24) & 0xff;video_tag[6] = (len >> 16) & 0xff;video_tag[7] = (len >> 8) & 0xff;video_tag[8] = len & 0xff;// 创建 RTMPPacketRTMPPacket packet;RTMPPacket_Reset(&packet);packet.m_body = (char *)dataBuffer + RTMP_MAX_HEADER_SIZE;packet.m_nBodySize = 9 + len;packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;packet.m_nChannel = 0x04; // 通道ID,音频或视频为0x04packet.m_nTimeStamp = dts_ms;packet.m_hasAbsTimestamp = 0;packet.m_headerType = RTMP_PACKET_SIZE_LARGE;packet.m_nInfoField2 = rtmp->m_stream_id;memcpy(packet.m_body, video_tag, 9);memcpy(packet.m_body + 9, data, len);// 检查连接的状态if (!RTMP_IsConnected(rtmp)){printf("rtmp disconnect!\n");return -1;}// 发送,0表示直接发送,1表示放进发送队列if (!RTMP_SendPacket(rtmp, &packet, 1)){std::cout << "RTMP_SendPacket failed" << std::endl;return -1;}return 0;
}

main.cpp:

/*
// 推流AAC#include <iostream>
#include <chrono>
#include <thread>
#include "aacParse.h"
#include "rtmp_ah_publish.h"using namespace std;
using namespace std::chrono;int main()
{int ret;AACParse aac;uint8_t profile, sampleRate_index, channel_number;uint8_t buf[1024 * 1024];uint32_t len = 0;Rtmp_ah_publish rtmp_publish;aac.open_file("/home/tl/work/app/res/output.aac");if (rtmp_publish.connect("rtmp://192.168.0.102/live/livestream") == -1){cout << "connect failed." << endl;aac.close_file();return 0;}aac.get_configInfo(profile, sampleRate_index, channel_number);rtmp_publish.send_aac_sequence_header(profile, sampleRate_index, channel_number);// 计算每帧的持续时间(毫秒)uint32_t frame_duration_ms = 1024.0 / aac.get_aacSampleRate(sampleRate_index) * 1000;cout << "frame_duration_ms: " << frame_duration_ms << endl;uint32_t dts = 0;// 记录开始时间auto start_time = high_resolution_clock::now();while ((ret = aac.get_adts(buf, sizeof(buf), len)) != -1){rtmp_publish.send_aacData(buf + aac.get_headLength(), len - aac.get_headLength(), dts);dts += frame_duration_ms;if (ret == 0)break;// 计算发送时间auto target_time = start_time + duration<double, milli>(dts);// 获取当前时间auto now = high_resolution_clock::now();if (target_time > now){// 计算需要等待的持续时间auto sleep_duration = target_time - now;// 转换为毫秒,并休眠this_thread::sleep_for(duration_cast<milliseconds>(sleep_duration));}}if (ret == -1){cout << "get_adts failed." << endl;}cout << "end" << endl;rtmp_publish.close();aac.close_file();return 0;
}
*//*
// 推流H264#include <iostream>
#include <chrono>
#include <thread>
#include <cstring>
#include "h264Parse.h"
#include "rtmp_ah_publish.h"#define FRAME_RATE 60 // 视频帧率using namespace std;
using namespace std::chrono;int main()
{int ret;H264Parse h264;uint8_t buf[1024 * 1024];uint32_t len = 0;Rtmp_ah_publish rtmp_publish;h264.open_file("/home/tl/work/app/res/output.h264");if (rtmp_publish.connect("rtmp://192.168.0.102/live/livestream") == -1){cout << "connect failed." << endl;h264.close_file();return 0;}uint8_t sps[64], pps[64];uint32_t spslen, ppslen;h264.get_sps_pps(sps, sizeof(sps), spslen, pps, sizeof(pps), ppslen);rtmp_publish.send_avc_sequence_header(sps + h264.get_startCode_len(sps), spslen - h264.get_startCode_len(sps),pps + h264.get_startCode_len(pps), ppslen - h264.get_startCode_len(pps));uint32_t frame_duration_ms = 1.0 / FRAME_RATE * 1000;cout << "frame_duration_ms: " << frame_duration_ms << endl;uint32_t dts = 0;int startCode_len = 0;// 记录开始时间auto start_time = high_resolution_clock::now();while ((ret = h264.read_nalu(buf, sizeof(buf), len, 1024 * 50)) != -1){startCode_len = h264.get_startCode_len(buf);if (rtmp_publish.send_h264Data(buf + startCode_len,len - startCode_len, dts) == 0){dts += frame_duration_ms;}if (ret == 0)break;// 计算发送时间auto target_time = start_time + duration<double, milli>(dts);// 获取当前时间auto now = high_resolution_clock::now();if (target_time > now){// 计算需要等待的持续时间auto sleep_duration = target_time - now;// 转换为毫秒,并休眠this_thread::sleep_for(duration_cast<milliseconds>(sleep_duration));}}if (ret == -1){cout << "read_nalu failed." << endl;}cout << "end" << endl;rtmp_publish.close();h264.close_file();return 0;
}
*/// 同时推流AAC和H264#include <iostream>
#include <chrono>
#include <thread>
#include <cstring>
#include <mutex>
#include "aacParse.h"
#include "h264Parse.h"
#include "rtmp_ah_publish.h"#define FRAME_RATE 60 // 视频帧率using namespace std;
using namespace std::chrono;std::mutex rtmp_mutex;void stream_aac(AACParse &aac, Rtmp_ah_publish &rtmp_publish, uint8_t sampleRate_index)
{int ret;uint8_t buf[1024 * 1024];uint32_t len = 0;uint32_t dts = 0;// 计算每帧的持续时间(毫秒)uint32_t frame_duration_ms = 1024.0 / aac.get_aacSampleRate(sampleRate_index) * 1000;cout << "AAC frame_duration_ms: " << frame_duration_ms << endl;// 记录开始时间auto start_time = high_resolution_clock::now();    while ((ret = aac.get_adts(buf, sizeof(buf), len)) != -1){// 上锁{std::lock_guard<std::mutex> lock(rtmp_mutex);rtmp_publish.send_aacData(buf + aac.get_headLength(), len - aac.get_headLength(), dts);}dts += frame_duration_ms;if (ret == 0)break;auto target_time = start_time + duration<double, milli>(dts);auto now = high_resolution_clock::now();if (target_time > now){auto sleep_duration = target_time - now;this_thread::sleep_for(duration_cast<milliseconds>(sleep_duration));}}if (ret == -1){cout << "AAC get_adts failed." << endl;}
}void stream_h264(H264Parse &h264, Rtmp_ah_publish &rtmp_publish)
{int ret;uint8_t buf[1024 * 1024];uint32_t len = 0;uint32_t dts = 0;uint32_t frame_duration_ms = 1.0 / FRAME_RATE * 1000;cout << "H264 frame_duration_ms: " << frame_duration_ms << endl;// 记录开始时间auto start_time = high_resolution_clock::now();while ((ret = h264.read_nalu(buf, sizeof(buf), len, 1024 * 50)) != -1){int startCode_len = h264.get_startCode_len(buf);// 上锁{std::lock_guard<std::mutex> lock(rtmp_mutex);if (rtmp_publish.send_h264Data(buf + startCode_len, len - startCode_len, dts) == 0){dts += frame_duration_ms;}}if (ret == 0)break;auto target_time = start_time + duration<double, milli>(dts);auto now = high_resolution_clock::now();if (target_time > now){auto sleep_duration = target_time - now;this_thread::sleep_for(duration_cast<milliseconds>(sleep_duration));}}if (ret == -1){cout << "H264 read_nalu failed." << endl;}
}int main()
{AACParse aac;H264Parse h264;Rtmp_ah_publish rtmp_publish;// 打开AAC文件aac.open_file("/home/tl/work/app/res/output.aac");// 打开H264文件h264.open_file("/home/tl/work/app/res/output.h264");// 连接RTMP流if (rtmp_publish.connect("rtmp://192.168.0.102/live/livestream") == -1){cout << "connect failed." << endl;aac.close_file();return 0;}uint8_t profile, sampleRate_index, channel_number;aac.get_configInfo(profile, sampleRate_index, channel_number);rtmp_publish.send_aac_sequence_header(profile, sampleRate_index, channel_number);uint8_t sps[64], pps[64];uint32_t spslen, ppslen;h264.get_sps_pps(sps, sizeof(sps), spslen, pps, sizeof(pps), ppslen);rtmp_publish.send_avc_sequence_header(sps + h264.get_startCode_len(sps), spslen - h264.get_startCode_len(sps),pps + h264.get_startCode_len(pps), ppslen - h264.get_startCode_len(pps));// 创建两个线程来同时推流AAC和H264thread aac_thread(stream_aac, ref(aac), ref(rtmp_publish), ref(sampleRate_index));thread h264_thread(stream_h264, ref(h264), ref(rtmp_publish));// 等待两个线程完成aac_thread.join();h264_thread.join();cout << "end" << endl;// 清理rtmp_publish.close();aac.close_file();h264.close_file();return 0;
}

相关文章链接:Flv 格式分析_script tag data-CSDN博客,注意文章中有处错误:

AVCDecoderConfigurationRecord 结构图中有一处错误,pictureParameterSetLength (PPS的长度) 应该是 UI16 ,两个字节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/12431.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

中国药品注册审批数据库- 药品注册信息查询与审评进度查询方法

药品的注册、审评审批进度信息是医药研发相关人员每天都会关注的信息&#xff0c;为了保证药品注册申请受理及审评审批进度信息的公开透明&#xff0c;CDE药审中心提供药品不同注册分类序列及药品注册申请受理的审评审批进度信息查询服务。但因CDE官网的改版导致很大一部分人不…

代数插值实验

实验类型&#xff1a;●验证性实验 ○综合性实验 ○设计性实验 实验目的&#xff1a;进一步熟练掌握Lagrange插值算法、Newton插值算法&#xff0c;提高编程能力和解决插值问题的实践技能。 实验报告&#xff1a;根据实验情况和结果撰写并递交实验报告。 实验报告打印和装…

物联网智能技术的深入探讨与案例分析

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

点云配准之点到点,点到面,点到线ICP,NDT算法介绍

点云配准&#xff08;Point Cloud Registration&#xff09;即求一个位姿变换 x [ R , t ] \mathbf{x}[\mathbf{R},\mathbf{t}] x[R,t]&#xff0c;将源点云 Q { q 1 , ⋯ , q m } Q\{\mathbf{q}_{1},\cdots,\mathbf{q}_{m}\} Q{q1​,⋯,qm​}变换到与目标点云 P { p 1 , ⋯…

Html5详解

目录 一、浏览器相关知识 二、html简介 (一)超文本标记语言 (二)HTML基础结构 (三)HTML概念词汇解释 (四)HTML的语法规则 (五)前端开发工具VS Code与插件 1.VS Code的安装 2.安装插件&#xff1a; 3.通过live Server 小型服务器运行项目 4.其他常见设置 5.在线帮…

实现 think/queue 日志分离

当我们使用think/queue包含了比较多的不同队列,日志会写到runtime/log目录下,合并写入的,不好排查问题,我们遇到一个比较严重的就是用了不同用户来执行,权限冲突了,导致部分队列执行不了. 为了解决以上问题,本来希望通过Log::init设置不同日志路径的,但是本地测试没生效,于是用…

创新不设限,灵码赋新能:通义灵码新功能深度评测

引言 自从2023年通义灵码发布以来&#xff0c;这款基于阿里云通义大模型的AI编码助手便迅速成为了开发者们心中的“明星产品”&#xff0c;受到了广大开发者的关注与好评。它不仅为个人开发者提供了强大的支持&#xff0c;帮助企业团队提升了研发效率&#xff0c;同时也推动了…

道品科技智慧农业中的物联网技术:生产与溯源系统的结合

随着全球人口的不断增长和城市化进程的加快&#xff0c;农业面临着巨大的挑战&#xff0c;包括资源短缺、环境污染和食品安全等问题。为了解决这些问题&#xff0c;智慧农业应运而生&#xff0c;其中物联网&#xff08;IoT&#xff09;技术的应用为农业的现代化提供了强有力的支…

【MPC-Simulink】EX03 基于非线性系统线性化模型MPC仿真(MIMO)

【MPC-Simulink】EX03 基于非线性系统线性化模型MPC仿真&#xff08;MIMO&#xff09; 参考 Matlab 官网提供的 Model Predictive Control Toolbox - Getting Started Guide&#xff0c;以零初始状态条件下的非线性系统在线性化后得到的多输入多输出&#xff08;MIMO&#xff…

期权开户难不难?期权开户成功后当天是否能交易

期权开户难不难&#xff1f;这取决于投资者的准备情况和所选的开户途径。对于满足一定资金和经验要求的投资者来说&#xff0c;通过正规期货公司或期权交易平台进行开户&#xff0c;虽然流程相对复杂&#xff0c;但只要遵循步骤&#xff0c;仍然可以顺利完成&#xff0c;下文为…

沈阳乐晟睿浩科技有限公司引领新潮流

在当今数字化浪潮汹涌的时代&#xff0c;电子商务以其独特的魅力和无限潜力&#xff0c;正深刻改变着人们的消费习惯与商业模式。沈阳乐晟睿浩科技有限公司&#xff08;以下简称“乐晟睿浩”&#xff09;&#xff0c;作为电商领域的一颗璀璨新星&#xff0c;凭借其深厚的技术实…

【一步步开发AI运动小程序】二十一、如果将AI运动项目配置持久化到后端?

**说明&#xff1a;**本文所涉及的AI运动识别、计时、计数能力&#xff0c;都是基于云智「Ai运动识别引擎」实现。云智「Ai运动识别」插件识别引擎&#xff0c;可以为您的小程序或Uni APP赋于原生、本地、广覆盖、高性能的人体识别、姿态识别、10余种常见的运动计时、计数识别及…

Python栈--深度优先搜索(迷宫问题)

给一个二维列表&#xff0c;表示迷宫(0表示给出算法&#xff0c;求通道&#xff0c;1表示围墙)。 给出算法&#xff0c;求一条走出迷宫的路径。 maze [ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], [1, 0, 0, 1, 0, 0, 0, 1, 0, 1], […

安卓主板_基于联发科MTK MT8788平台平板电脑方案_安卓核心板开发板定制

联发科MT8788安卓核心板平台介绍&#xff1a; MTK8788设备具有集成的蓝牙、fm、wlan和gps模块&#xff0c;是一个高度集成的基带平台&#xff0c;包括调制解调器和应用处理子系统&#xff0c;启用LTE/LTE-A和C2K智能设备应用程序。该芯片集成了工作在2.0GHz的ARM Cortex-A73、最…

LabVIEW 版本控制

在软件开发中&#xff0c;版本控制系统&#xff08;VCS&#xff09; 是管理代码版本变化的核心工具。对于 LabVIEW 用户&#xff0c;虽然图形化编程带来高效开发体验&#xff0c;但由于其特有的二进制 VI 文件格式&#xff0c;传统文本比较工具无法直接用于 LabVIEW 项目。这时…

centos7.9部署oracle19c教程

1.安装前准备 1.1关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalldvi /etc/selinux/config1.2 安装依赖 yum install -y unzip compat-libcap1 compat-libstdc-33 gcc-c ksh libaio-devel libstdc-devel elfutils-libelf-devel fontconfig-devel …

034集——JIG效果实现(橡皮筋效果)(CAD—C#二次开发入门)

可实现效果如下&#xff08;对象捕捉F3需打开&#xff0c;否则效果不好&#xff09;&#xff1a; public class CircleJig : EntityJig{public static void DraCJig(){PromptPointResult ppr Z.ed.GetPoint("a");if (ppr.Value null) return;Point3d pt ppr.Value…

Softing工业将在纽伦堡SPS 2024上展示Ethernet-APL现场交换机

今年&#xff0c;Softing工业将在纽伦堡SPS贸易展览会上展示aplSwitch Field —— 一款先进的过程自动化解决方案。这款16端口以太网高级物理层&#xff08;APL&#xff09;现场交换机的防护等级高达IP30&#xff0c;可提供从应用到现场级别的无缝以太网连接&#xff0c;专为Ex…

鸿蒙UI开发——小图标的使用

1、前 言 鸿蒙SDK中为我们提供了大量的高质量内置图标&#xff0c;图标详见(https://developer.huawei.com/consumer/cn/design/harmonyos-symbol/) 图标资源一览&#xff1a; 除了基本的图标图形外&#xff0c;我们还可以支持图标的多种填充模式&#xff08;单色、多色、分层…

python3的基本数据类型:Dictionary(字典)的创建

一. 简介 本文开始简单学习一下 python3中的一种基本数据类型&#xff1a;Dictionary&#xff08;字典&#xff09;。 字典&#xff08;dictionary&#xff09;是Python中另一个非常有用的内置数据类型。 二. python3的基本数据类型&#xff1a;Dictionary&#xff08;字典&…