海康、国标PS流数据解析

国标GB28181软件LiteGBS国标GB28181-2022平台如何从海康摄像头获取PS流 本例模拟了一个SIP Server(UAS),设置摄像头注册到上面,之后模拟了一个SIP Client(UAC)向摄像头的IP地址发送获取视频的请求,之后监听UDP的端口来接收摄像头发送过来的PS流。按照GB28181的流程,SIP Server需要对设备进行验证,然后才能允许其注册,不过这里可以取巧,就是不进行验证,IPC注册就返回注册成功,心跳也是。启用 GB28181 的平台接入方式,SIP服务器ID填写SIP服务的用户Uri,地址填写运行刚才的模拟SIP Server的IP地址。 阅读详情

    最近做的一个项目,需要对接海康摄像头传过来的视频数据,经过分析知道传过来的是PS封装后的数据,不能直接播放,需要把PS流解析成h264裸流。经过研究有两种方法:1.直接扔给ffmpeg解析,这种方法需要用AVIOContext自定义输入;2.自己解析PS流,这种方法比较直接,只要熟悉PS解析的流程就可以做。我两种方法都实现了,刚开始是用ffmpeg,后来发现因为需要开线程,异步读写,复杂性较高,然后想着能不能自己解析PS,于是网上找了很多资料,终于也实现了,自己解析PS就是比较简单,不用开线程,不用异步处理,只要调用一个算法就能得出解析结果。这里记录下两种方法的实现,希望对后来者有所帮助。

 一、采用ffmpeg解析PS流

首先需要用AVIOContext自定义输入,我封装为一个H264Reader类,如下:

#pragma once
#include <stdio.h>
#include <thread>
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#include "utils.h"

class H264Reader
{
public:
	H264Reader(DevStreamHandle* aStreamHandle);

	int Run(DevStreamHandle* aStreamHandle);
	virtual ~H264Reader();
	void Stop() {
		this->running = false;
	}
private:

	AVFormatContext *ic = NULL;
	AVIOContext *avio = NULL;
	unsigned char *avio_ctx_buffer = NULL;

public:
	bool running = true;

};

#include "H264Reader.h"
#include "BlockingRingBuffer.h"
#include <iostream>
using namespace std;

H264Reader::H264Reader(DevStreamHandle* aStreamHandle)
{
	std::thread([&](DevStreamHandle *handle) {
		Run(handle);
	}, aStreamHandle).detach();
}

static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
	DevStreamHandle *handle = (DevStreamHandle*)opaque;
	En_BlockingRingBuffer_FetchResult ret = handle->ringBuff->Fetch(buf, buf_size); //
	
	if (ret == RBFR_OK)
		return buf_size;
	else {
		return 0;
	}
}

int H264Reader::Run(DevStreamHandle* stream_handle)
{
	int isopen = -1;
	size_t avio_ctx_buffer_size = 1024*32;// 
	ic = avformat_alloc_context();
	avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size);
	avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, stream_handle, read_packet, NULL, NULL);
	ic->pb = avio;
	isopen = avformat_open_input(&ic, NULL, NULL, NULL);
	if (isopen < 0) {
		printf("Could not open input----->>>\n");
		return -1;
	}
	AVPacket *pkt = av_packet_alloc();
	while (running)
	{
		int re = av_read_frame(ic, pkt);
		if (re != 0){
			av_packet_unref(pkt);
			printf("~~~~~~~~~av_read_frame()= %d\n", re);
			continue;
		}
		//这里就把h264裸流数据投送给需要的地方
		//PostData((char*)pkt->data, pkt->size, stream_handle);
		av_packet_unref(pkt);
	}
	if (avio) {
		av_freep(&avio->buffer);
		av_freep(&avio);
	}
	return 0;
}

H264Reader::~H264Reader()
{
	//av_free(avio_ctx_buffer);
}

 

H264Reader类开启一个线程,在线程里调用H264Reader的Run()函数。使用AVIOContext,需要定义一个读回调函数,上述代码 avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, read_packet, NULL, NULL) 这行就是把自定义回调函数read_packet绑定到AVIOContext,其中第四个参数是用户数据,这里传入DevStreamHandle结构体类型stream_handle,可以在回调里面获取这个变量。在回调里面给ffmpeg写入PS流,ffmpeg内部会不断调用这个回调,然后在循环里面不断调用 av_read_frame(ic, pkt),返回的AVPacket变量pkt就是已经被ffmpeg解析后的h264裸流。

使用AVIOContext自定义输入的难点在于需要设计一个缓冲区,写入线程需要向缓冲区写入不定大小的数据,而读取线程需要向缓冲区读取不定大小的数据,因为写入和读取的大小都不固定所以不能定义一个固定大小的缓冲区,这里我采用一个环形阻塞缓冲链表BlockingRingBuffer,这个类的实现请看我文章最后附上的下载链接。

在写入线程需要调用BlockingRingBuffer的Cat()函数写入PS流:


// 取到PS流,存储在ringBuff
int  FacdNetMediaServer::cbPreStreamMedia(HSTREAM hStream, const StreamMediaFrame *cFrame, DWORD dwUserData)
{
	DevStreamHandle *pHandle = (DevStreamHandle*)dwUserData;
	if (pHandle != NULL && pHandle->ringBuff != NULL){
		pHandle->ringBuff->Cat((const unsigned char*)cFrame->cFrameBuffer.pBuffer, cFrame->cFrameBuffer.dwBufLen);
	}
	return 0;
}

 在AVIOContext的读回调里面调用BlockingRingBuffer的Fecth()函数读取PS流:

static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
	H264Reader *h264 = (H264Reader*)opaque;
	DevStreamHandle *handle = h264->streamHandle;
	En_BlockingRingBuffer_FetchResult ret =handle->ringBuff->Fetch(buf, buf_size); //
	
	if (ret == RBFR_OK)
		return buf_size;
	else {
		return 0;
	}
}

二,采用自己解析PS流的方法

这个方法就简单多了,主要思想是两个PS流头部之间的包拼接成一个数据包扔给PS解析函数处理,得出的就是一个h264帧,PS头以0x000001ba标识,所以拼接代码如下:

#define MAX_FRAME_SIZE 1024*1024*5
char *h264_pkt=new char[MAX_FRAME_SIZE];
int pkt_len;
bool isfirst = true;
int  cbRealStream(char* ps_buffer,int len)
{
	if (ps_buffer != NULL && len>0){
		char *h264Frame;
		int h264Len;
		
		//printf("%02x %02x %02x %02x\n", ps_buffer[0], ps_buffer[1], ps_buffer[2], ps_buffer[3]);
		//查找ps头 0x000001BA
		if (ps_buffer[0] == '\x00' && ps_buffer[1] == '\x00' && ps_buffer[2] == '\x01' && ps_buffer[3] == '\xba')
		{
			if (!isfirst)
			{
				//此包为ps新的一帧,每次到这里都先处理存储好的前一帧
				GetH246FromPs(ps_pkt, pkt_len, &h264Frame, &h264Len);
				//海康流特殊处理部分:分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,直接舍弃.00 00 01 bd和 00 00 01 c0为私有标志和音频数据舍弃
				if (h264Frame[0] >> 5 == '\x06' || h264Frame[0] >> 5 == '\x09' || h264Frame[0] >> 5 == '\x0a' || h264Frame[0] >> 5 == '\x0b' || h264Frame[0] >> 5 == '\x0c')
				{
				}
				else {//这就是获取的含有标准正常H264数据的帧 我们开始进行处理,将该帧存入Deque,该deque只负责存储所有的一帧帧的数据
					bool ret = pHandle->cam->deliverFrame(h264Frame, h264Len);
					
				}
			}
			//各个变量初始化,开始拼接下一个帧(可能收到的N个包才能组成一个帧,所以这里有一个拼接操作,主要是一个帧的头部指针一直memcpy,把内存向后叠加)
			pkt_len = 0;
			memset(ps_pkt, 0, MAX_FRAME_SIZE);
			memcpy(ps_pkt + pkt_len, ps_buffer, len);
			pkt_len += len;
			isfirst = false;
		}
		else {//当然如果开头不是0x000001BA,默认为一个帧的中间部分,我们将这部分内存顺着帧的开头向后存储
			if (!isfirst)
			{
				//排除音频和私有数据
				if (ps_buffer[0] == '\x00' && ps_buffer[1] == '\x00' && ps_buffer[2] == '\x01' && (ps_buffer[3] == '\xc0' || ps_buffer[3] == '\xbd'))
				{
					return 0;
				}
				//这是正常的帧数据
				if (pkt_len + len > MAX_FRAME_SIZE) {
					LogError("帧大小超过5MB");
					return -1;
				}
				memcpy(ps_pkt + pkt_len, ps_buffer, len);
				pkt_len += len;
			}
		}
		
	}
	return 0;
}

拼接好PS帧之后调用PS解析函数GetH246FromPs()就能得到h264数据,PS解析的函数定义如下:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(1)

union littel_endian_size
{
	unsigned short int	length;
	unsigned char		byte[2];
};

struct pack_start_code
{
	unsigned char start_code[3];
	unsigned char stream_id[1];
};

struct program_stream_pack_header
{
	pack_start_code PackStart;// 4
	unsigned char Buf[9];
	unsigned char stuffinglen;
};

struct program_stream_map
{
	pack_start_code PackStart;
	littel_endian_size PackLength;//we mast do exchange
								  //program_stream_info_length
								  //info
								  //elementary_stream_map_length
								  //elem
};

struct program_stream_e
{
	pack_start_code		PackStart;
	littel_endian_size	PackLength;//we mast do exchange
	char				PackInfo1[2];
	unsigned char		stuffing_length;
};

#pragma pack()

int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
	//通过 00 00 01 ba头的第14个字节的最后3位来确定头部填充了多少字节
	program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;
	unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';

	*leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//减去头和填充的字节
	*NextPack = Pack + sizeof(program_stream_pack_header) + pack_stuffing_length;

	if (*leftlength<4) return 0;

	//printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);

	return *leftlength;
}

inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);

	program_stream_map* PSMPack = (program_stream_map*)Pack;

	//no payload
	*PayloadData = 0;
	*PayloadDataLen = 0;

	if (length < sizeof(program_stream_map)) return 0;

	littel_endian_size psm_length;
	psm_length.byte[0] = PSMPack->PackLength.byte[1];
	psm_length.byte[1] = PSMPack->PackLength.byte[0];

	*leftlength = length - psm_length.length - sizeof(program_stream_map);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if (*leftlength <= 0) return 0;

	*NextPack = Pack + psm_length.length + sizeof(program_stream_map);

	return *leftlength;
}

inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
	program_stream_e* PSEPack = (program_stream_e*)Pack;

	*PayloadData = 0;
	*PayloadDataLen = 0;

	if (length < sizeof(program_stream_e)) return 0;

	littel_endian_size pse_length;
	pse_length.byte[0] = PSEPack->PackLength.byte[1];
	pse_length.byte[1] = PSEPack->PackLength.byte[0];
	if (pse_length.length + 6 > length)
	{
		pse_length.length = length - 6;
		/*int PES_packet_length_fixed = length - 6;
		Pack[4] = (char)((PES_packet_length_fixed && 0xFF00) >> 8);
		Pack[5] = (char)(PES_packet_length_fixed & 0x00FF);*/
	}

	*PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;
	if (*PayloadDataLen>0)
		*PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;

	*leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if (*leftlength <= 0) return 0;

	*NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;

	return *leftlength;
}

int inline GetH246FromPs(char* buffer, int length, char **h264Buffer, int *h264length)
{
	int leftlength = 0;
	char *NextPack = buffer;

	*h264Buffer = buffer;
	*h264length = 0;

	leftlength = length;

	if (ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength) == 0)
		return 0;

	char *PayloadData = NULL;
	int PayloadDataLen = 0;

	while (leftlength >= sizeof(pack_start_code))
	{
		PayloadData = NULL;
		PayloadDataLen = 0;

		
		if (NextPack
			&& NextPack[0] == '\x00'
			&& NextPack[1] == '\x00'
			&& NextPack[2] == '\x01'
			&& NextPack[3] == '\xE0')
		{
			//接着就是流包,说明是非i帧
			if (Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))
			{
				if (PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}
			}
			else
			{
				if (PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}

				break;
			}
		}
		else if (NextPack
			&& NextPack[0] == '\x00'
			&& NextPack[1] == '\x00'
			&& NextPack[2] == '\x01'
			&& NextPack[3] == '\xBC')
		{
			if (ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen) == 0)
				continue;
		}
		else
		{
			//printf("no konw %x %x %x %x\n", NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
			break;
		}
	}


	return *h264length;
}

最后附上这篇文章相关的代码下载地址https://download.youkuaiyun.com/download/wdglhack/15930839

 

GB/T28181协议实战:5分钟搞定海康摄像头国标平台注册(附SIP报文分析) 本文实战解析海康威视摄像头通过GB/T28181协议接入国标平台的核心步骤与常见问题。重点拆解了设备注册流程中的SIP报文交互与401认证机制,并详细分析了心跳保活及掉线排查方法,通过真实抓包案例帮助工程师快速定位并解决接入难题。 阅读详情

相关推荐

使用FFmpeg从内存中读取PS/TS流并分离音频和视频

在本文中,我们将介绍如何使用FFmpeg库从内存中读取PS/TS流,并将其分离成独立的音频和视频流。希望这个示例能够帮助你使用FFmpeg从内存中读取PS/TS流并分离音频和视频。如果你有其他关于FFmpeg的问题,或者需要更多的帮助,请随时提问。以上代码演示了如何使用FFmpeg库从内存中读取PS/TS流,并将其分离成音频和视频流。首先,确保你已经安装了FFmpeg库,并且可以在你的编程环境中使用它。最后,记得释放分配的资源,包括关闭输入流、释放内存和AVFormatContext等对象。

EtwCpp的博客 683

PS、TS流解析

参考ZLMediaKit处理PS、TS流

u013227967的博客 951

wireshark分析PS流格式解析详解

手把手教你 wireshark ps流解析

weixin_44834554的博客 3549

海康摄像头PS流格式解析(RTP/PS/H264)

概览 上文记录了如何模拟 SIP Server 和 SIP Client 基于 GB28181 从海康 IPC 摄像头获取 PS 流,本篇文章结合实例,用于记录分析推送过来的 PS 流的内容及结构。 上图为国标 GB28181 关于数据封装说明,能够接入 GB28181 平台的 IPC 符合上述标准。 先行准备 如果已经能够接收到海康的 RTP 流,那么用 wireshark 来看就行,或者下载我本次使用的二进制数据,它就是 wireshark 的 Payload 部分:hikvision-ps

chenxijie1985的专栏 9531

GB28181 码流解析( 四 )

GB28181 码流解析( 四 ) GB28181最关键的就是码流解析了,本文解析海康, 大华摄像头的码流, 解析出来h264码流。 海康, 大华摄像头的码流是 根据国标GB/T28181—2016C.1 基于 RTP的视音频数据PS封装,那么我们就要就要解析rtp包, 然后再解析ps流, 最后获取h264视频流。 既然咋是做流媒体开发, 那就绕不开rtp, 所以还是要掘地三尺, 把它搞个明明白白, ...

Lonely_Bird的博客 3127

海康国标PS流解析实战:从裸流到可用帧的完整指南

在视频监控与流媒体接入领域,PS流解析是处理GB/T 28181设备数据的基础技术。其核心原理在于将复合流中的PS包头、系统头、PES包逐层剥离,提取出独立的H.264/H.265视频基本流与G.711/AAC音频基本流。掌握这一技术,能有效解决视频平台接入、流媒体网关及录像文件二次处理中的封装识别与帧提取难题。通过状态机切分、PES包头解析及跨包拼接,可输出可解码的裸流文件,并借助ffprobe验证完整性。本文聚焦海康、国标PS流解析,分享从裸流到可用帧的工程实践与避坑经验。

weixin_29191761的博客 242

GB28181的PS流分析: 封装 / 分包发送 / 接收组包 / 解析

GB28181的PS流分析: 封装 / 分包发送 / 接收组包 / 解析

cry_smile_hate的专栏 3369

国标GB/T28181 PS流解析:从RTP到H.264的层层拆解

视频监控接入中,国标GB/T28181的媒体面常采用RTP承载PS流,而PS流内部又封装了H.264视频和G.711音频。这种多层嵌套结构,让很多开发者即使抓到码流也无法直接播放,黑屏、花屏、音画不同步等问题频发。PS流本质上是MPEG-2系统层定义的容器格式,通过起始码、PSM、PES逐层组织数据,并依赖SCR与PTS完成音视频同步。理解这一套封装与解析原理,是处理国标设备媒体流的基础。实际工程中,还需要应对厂商私有扩展头带来的偏移问题,以及关键帧标记、分包策略等非标准细节。从RTP载荷出发,识别私有头、

weixin_28738397的博客 282

海康威视HCNetSDK/ISAPI/RTSP对接实战:从登录取流到稳定上线

视频监控系统接入的核心,是把设备能力抽象为可调用的网络接口与流媒体协议。常见方案中,HCNetSDK 负责私有协议长连接和实时流,ISAPI 提供 HTTP/XML 的查询配置能力,RTSP 则适合标准取流播放。理解认证、通道号、字符集、时区与句柄生命周期等基础机制,才能避免登录失败、预览黑屏和资源泄漏。在安防集成、NVR 摄像头接入、云台控制与录像回放等场景中,这些细节直接决定系统能否稳定上线。围绕海康威视设备从能跑通到能上线的工程实践,选型边界、参数含义和排查链路因此变得尤为关键。

weixin_34208283的博客 524

GB28181国标PS流解包实战:从RTP重组到H.264/H.265裸流提取

在视频监控与流媒体传输领域,设备输出的音视频数据往往经过复杂的容器封装,而RTP作为实时传输协议,承载着这些封装后的码流。PS流(Program Stream)是MPEG-2系统层定义的一种程序流容器,常被用于GB28181国标设备(如海康、大华摄像头)的媒体传输。它通过系统时钟参考、节目流映射表(PSM)和PES包结构,将H.264/H.265视频与音频复用在一起,并携带帧边界及时间戳信息。相比于直接解析裸RTP负载,正确解复用PS流能够更可靠地还原视频帧,服务于视频接入平台、SIP网关等场景。本文针对海

weixin_31528001的博客 283

海康、国标ps流解析.rar

分为ffmpeg解析版本和直接解析PS版本,相关博客请看https://blog.youkuaiyun.com/wdglhack/article/details/114999974

PS封装格式:GB28181协议RTP传输

转载 PS封装格式:GB28181协议RTP传输 1. GB28181要求的RTP流格式 2. 头部信息     在安防行业,有个协议是无论如何都要适配的,因为公安监控网络用的就是它,它就是:GB28181。而这份协议主要由海康制定,所以除了海康其他厂商想要适配都会少许有点儿麻烦。题主前东家便是海康,这里稍微分析下该协议几个容易搞混的细节,记录在此,方便以后自己查阅,也方便诸位。 1. GB28181要求的RTP流格式     首先,我们来看看I帧的.

B.souls 1008

ps流解析器,解析本地ps文件为 es文件,可分离出音频和视频

ps流解析器,可以解析本地ps文件,支持把ps文件转化为es的视频和音频文件

uniapp App播放海康ws流:从格式识别到落地实现

WebSocket协议作为全双工通信通道,在实时视频传输中扮演着关键角色。海康摄像头的流媒体服务常通过ws地址推送二进制视频帧,但移动端直接播放并非易事。理解数据封装格式(如FLV、裸H.264、PS流)以及编码与鉴权细节,是选型的基础。在uniapp这一跨端框架下,video组件无法直接消费ws流,采用成熟live-player插件或自研原生解码方案成为主要路径。从流格式识别、播放方案对比到延迟调优与常见坑点排查,系统梳理了App端接入海康ws流的关键环节,为监控类应用开发者提供可落地的实践参考。

weixin_29025501的博客 257

ps流解析器,可以解析本地ps文件,支持把ps文件转化为es的视频和音频文件.zip

ps流解析器,可以解析本地ps文件,支持把ps文件转化为es的视频和音频文件.zip

revit二次开发环境搭建(2024版)(小白友好)

revit二次开发环境配置,SDK、addin manager和lookup新手友好安装教程

m0_56961251的博客 1420

网络传输H.264方法对比

网络传输H.264方法对比

崔杰城的博客 954

Claude Code+Cursor+Claude 4三剑合璧!5步搞定Magentic-UI的OAuth2.0集成,开发效率飙升500%,新手秒变全栈工程师

本文详细介绍了如何利用Claude Code、Cursor和Claude 4三款AI工具高效集成Magentic-UI的OAuth2.0认证系统。通过5个步骤的实战演练,开发者可快速完成从环境搭建、项目分析、代码生成到前端集成的全过程,显著提升开发效率。文章还提供了JWT令牌管理、安全配置及常见问题解决方案。

weixin_29234509的博客 453

如何基于EasyDSS流媒体RTMP、HLS(m3u8)、HTTP-FLV、RTSP服务器体系的全套SDK完成各种场景下的视频应用需求...

需求背景 回顾EasyDSS的发展过程,基本上保持的是先局部后系统、先组件后平台的发展方式,一步一步夯实每一个细节功能点,从最基础、最兼容的音...

weixin_30856965的博客 173

SAP MRKO与MIRO的对比分析:为何寄售结算需要定制开发?

本文深入分析了SAP系统中MRKO与MIRO在寄售结算中的功能差异,揭示了标准MRKO在财务控制和业务流程上的局限性。通过定制开发实践,如BAdI增强和界面优化,企业可显著提升结算效率与准确性。文章还探讨了未来SAP S/4HANA在寄售结算中的智能化趋势,为相关企业提供技术参考。

weixin_29300241的博客 424

Windows 11 安装 Maven:下载、安装、配置国内镜像、验证

本文详细介绍了在 Windows 11 系统中安装与配置 Apache Maven 的完整流程,包括 JDK 环境准备、Maven 下载解压、本地仓库路径设置、阿里云国内镜像加速配置、环境变量设置及安装验证。同时提供了常用 Maven 命令速查表,帮助开发者快速上手依赖管理与项目构建,提升开发效率。

寒水馨 · 轻量级技术文档 · hanshuixin.org 1505
上一篇: export LD_LIBRARY_PATH 的使用
下一篇: ffmpeg解码h264流数据并用opencv显示
惊群
博客等级 码龄16年 12粉丝 5原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值