云计算百科
云计算领域专业知识百科平台

嵌入式平台live555 RTSP服务器编译与多路流优化实战

1. 嵌入式平台live555交叉编译实战

在嵌入式Linux环境下编译live555,最让人头疼的就是交叉编译工具链的配置和依赖库的链接问题。我当年第一次在ARM平台编译live555时,整整折腾了两天,踩了不少坑。这里分享一些实战经验,帮你快速绕过这些坑。

先下载最新的live555源码包,解压后进入目录。关键步骤是配置交叉编译环境,官方提供了一个config.armlinux模板,我们可以复制一份进行修改:

tar -xzvf live.2024.04.19.tar.gz
cd live
cp config.armlinux config.myarmlinux

编辑config.myarmlinux文件,重点修改以下几个参数:

CROSS_COMPILE?= /your/toolchain/path/arm-linux-gnueabi-
COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1
C_COMPILER = $(CROSS_COMPILE)gcc
CPLUSPLUS_COMPILER = $(CROSS_COMPILE)g++
CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1 -Wno-deprecated -std=c++11
LINK = $(CROSS_COMPILE)g++ -o
PREFIX = /your/install/path

这里有几个关键点需要注意:-std=c++11选项必须加上,因为新版本的live555使用了C++11特性。如果遇到std::atomic_flag没有test方法的错误,需要修改为test_and_set()。

生成Makefile并编译:

./genMakefiles myarmlinux
make -j4

编译完成后经常遇到"undefined reference"错误,这是因为库链接顺序不对。正确的链接顺序应该是:libliveMedia.a、libBasicUsageEnvironment.a、libgroupsock.a、libUsageEnvironment.a。确保libliveMedia.a放在第一位。

2. 多路视频流性能优化策略

在安防监控或无人机图传场景中,经常需要同时处理多路视频流。这时候性能优化就特别重要了。我做过一个8路D1视频流的项目,最初CPU占用率高达60%,经过优化后降到了30%以下。

双队列缓冲设计是关键优化点。原来的单队列设计存在视频帧和音频帧混合的问题,当服务器需要视频数据时,如果队列尾部是音频帧,就会导致取不到数据而等待。我们改为音视频分离的双队列:

typedef struct {
unsigned char* video_buffer[CHANNEL_NUM];
unsigned char* audio_buffer[CHANNEL_NUM];
int video_front[CHANNEL_NUM], video_rear[CHANNEL_NUM];
int audio_front[CHANNEL_NUM], audio_rear[CHANNEL_NUM];
} DualRingBuffer;

这样设计后,视频流和音频流互不干扰,大大减少了队列满的情况。在实际测试中,8路全开运行两天都没有出现队列满的问题。

内存零拷贝优化是另一个重点。原来的流程中有多次内存拷贝:

  • SNDeviceSource到H264VideoStreamDiscreteFramer一次拷贝
  • H264FUAFragmenter到H264VideoRTPSink又一次拷贝

我们通过传递指针而不是拷贝数据来实现零拷贝:

void SNDeviceSource::doGetNextFrame() {
// 原来需要memcpy(fTo, fVideoFrame, fFrameSize);
// 改为直接传递指针
fTo = fVideoFrame;
fFrameSize = fVideoFrameSize;
afterGetting(this);
}

同时修改MultiFramedRTPSink中的packFrame和afterGettingFrame1函数,使用iovec结构体来管理分散的内存块:

struct iovec fVecBuf[6];
fVecBuf[3].iov_base = rtpHeader;
fVecBuf[4].iov_base = fragmentHeader;
fVecBuf[5].iov_base = frameData;

对于TCP传输,直接使用writev系统调用发送分散的内存块。对于UDP,由于需要connect到特定对端,我们仍然需要将数据拷贝到连续内存中再用sendto发送。

3. 实时流媒体服务器定制开发

live555自带的testH264VideoStreamer示例只能播放文件,我们需要将其改造为支持实时视频流的服务器。核心是继承并重写关键类。

首先创建自定义的FramedSource子类:

class LiveCameraSource : public FramedSource {
public:
static LiveCameraSource* createNew(UsageEnvironment& env, int cameraId);

protected:
LiveCameraSource(UsageEnvironment& env, int cameraId);
virtual ~LiveCameraSource();

private:
virtual void doGetNextFrame();
static void deliverFrameStub(void* clientData);
void deliverFrame();

private:
int fCameraId;
unsigned char* fBuffer;
};

在doGetNextFrame中实现从摄像头获取数据:

void LiveCameraSource::doGetNextFrame() {
// 从摄像头驱动获取一帧数据
int frameSize = get_camera_frame(fCameraId, fBuffer, fMaxSize);
if (frameSize > 0) {
fFrameSize = frameSize;
fTo = fBuffer; // 零拷贝传递指针
afterGetting(this);
} else {
handleClosure();
}
}

然后创建自定义的ServerMediaSubsession:

class LiveCameraMediaSubsession : public OnDemandServerMediaSubsession {
public:
static LiveCameraMediaSubsession* createNew(UsageEnvironment& env, int cameraId);

protected:
virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
};

最后在主函数中创建RTSP服务器并添加会话:

RTSPServer* rtspServer = RTSPServer::createNew(env, 8554);
ServerMediaSession* sms = ServerMediaSession::createNew(env, "live", "Live Camera", "Live stream");
sms->addSubsession(LiveCameraMediaSubsession::createNew(env, 0));
rtspServer->addServerMediaSession(sms);

4. 实战问题排查与性能调优

在实际部署中,经常会遇到各种问题。比如客户端连接数多了之后性能下降,或者出现内存泄漏。这里分享几个排查技巧。

CPU占用率过高的排查:使用top命令查看CPU占用情况,如果发现CPU占用过高,可以使用perf工具进行性能分析:

perf top -p <pid>
perf record -p <pid> -g
perf report

常见的性能瓶颈可能在内存拷贝、锁竞争或者频繁的系统调用上。通过perf可以快速定位到热点函数。

内存泄漏的排查:使用valgrind工具检测内存泄漏:

valgrind –leak-check=full –show-leak-kinds=all ./your_program

在live555中,需要特别注意Medium对象的引用计数,确保正确调用close()方法。

网络性能优化:对于高并发的场景,可以调整内核网络参数:

echo 1024 > /proc/sys/net/core/somaxconn
echo 65535 > /proc/sys/net/ipv4/ip_local_port_range

还可以设置TCP_NODELAY选项减少延迟:

int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

多路流负载均衡:当处理多路视频流时,可以考虑使用多个线程或进程来处理不同的流。虽然live555是单线程设计,但可以在数据源层面做并行化:

void* video_capture_thread(void* arg) {
int camera_id = *(int*)arg;
while (1) {
Frame frame = capture_frame(camera_id);
push_to_queue(camera_id, frame);
notify_new_frame(camera_id);
}
}

每个摄像头一个采集线程,然后将数据推送到对应的队列中,由live555的主线程来取数据。这样可以利用多核CPU的优势。

经过这些优化后,在嵌入式平台上处理8路D1视频流,CPU占用率可以控制在30%左右,完全满足实时性要求。记得在正式部署前进行长时间的压力测试,确保系统的稳定性。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 嵌入式平台live555 RTSP服务器编译与多路流优化实战
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!