Darwin推流存储实现介绍 之二

2024-04-29 02:08

本文主要是介绍Darwin推流存储实现介绍 之二,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

根据上文确定的方案,我们会在每一个推流的会话建立时,增加一个自定义的Output对象,用来实现吧接收到的RTP包写到文件中。


首先,我们在RTSPReflectorOutput.cpp中,参考RTPSessionOutput类定义一个RTPSessionSaveOutput类:


class RTPSessionSaveOutput: public ReflectorOutput
{
public:RTPSessionSaveOutput(ReflectorSession* inReflectorSession, char* theFileName = NULL);virtual ~RTPSessionSaveOutput();//关键是重写这个方法,完成RTP包的存储。QTSS_Error  WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSec, Bool16 firstPacket );ReflectorSession* GetReflectorSession();//{ return fReflectorSession; }void        TearDown() {}Bool16      IsUDP() { return true;}Bool16      IsPlaying() { return true;}bool MkFileDirandFile();void Write_Header();private:ReflectorSession*       fReflectorSession;char        			 fFilePath[MaxPathSize];char        			 fFileName[255];SInt32      			 ffd;  // file handleBool16  bLastBlockFlg;//SInt64  lastArrivalTimeMSec;         
};


并完成实现:

#define DEFAULTPATH "/usr/local/movies/pushstream/"
RTPSessionSaveOutput::RTPSessionSaveOutput(ReflectorSession* inReflectorSession, char* theFileName):fReflectorSession(inReflectorSession),ffd(-1)
{printf("------- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ,theFileName =%s \n", theFileName);::memset(fFileName, 0, sizeof(fFileName));::memset(fFilePath, 0 , sizeof(fFilePath));bLastBlockFlg = false;lastArrivalTimeMSec = -1;strcpy(fFilePath, DEFAULTPATH);char *firstPos = ::strstr(theFileName, ".sdp");if (firstPos != NULL){int length = strlen(theFileName);int i = length - 1;int end = i;while(i > 0){if (theFileName[i] == firstPos[0]){end = i;}if (theFileName[i] == '/'){printf("----setup path:%d, i:%d-------\n", end, i);break;}i--;}if (i > 0 && end > i + 1){memcpy((char *)fFileName, ((char*)theFileName + i + 1), end - i - 1);fFileName[end - i - 1] = '\0';sprintf(fFilePath, "%s%s", DEFAULTPATH, fFileName);printf("----fFileName:%s, path:%s-------\n", fFileName, fFilePath);}}MkFileDirandFile();printf("-------  RTPSessionSaveAsMP4,  GetNumStreams=%d \n", inReflectorSession->GetNumStreams());this->InititializeBookmarks( inReflectorSession->GetNumStreams());}RTPSessionSaveOutput::~RTPSessionSaveOutput()
{printf("------- YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY \n");if(ffd != -1){close(ffd);ffd = -1;}::memset(fFileName, 0, sizeof(fFileName));::memset(fFilePath, 0 , sizeof(fFilePath));
}bool RTPSessionSaveOutput::MkFileDirandFile()
{        if(access(fFilePath, R_OK) != 0) // dir exists{int isCreate = mkdir(fFilePath , S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);if( !isCreate )    qtss_printf("create dir ok, %s\n", fFileName);else{qtss_printf("create %s failed! error code : %d\n", fFilePath, isCreate);return false;}}qtss_printf("---------- dir is ok ---------------%s\n", fFileName);    time_t	tmt =  OS::UnixTime_Secs();	 // Seconds since 1970  				char szBuf[128] = {0};        strftime(szBuf, sizeof(szBuf), "%Y-%m-%d_%H_%M_%S", localtime(&tmt));printf(" start0 =  %s\n", szBuf);if(-1 == ffd){sprintf(fFilePath, "%s/%s.m4a", fFilePath, szBuf);printf("-------open file , fFilePath=%s  \n", fFilePath);ffd = open(fFilePath, O_CREAT  | O_WRONLY  | O_APPEND, 0666);if (ffd != -1){//Write_Header();}}return true;
}ReflectorSession* RTPSessionSaveOutput::GetReflectorSession() 
{qtss_printf("################################## RTPSessionSaveAsMP4::GetReflectorSession \n");return fReflectorSession; 
}void RTPSessionSaveOutput::Write_Header()
{if (1){unsigned char header[4]; //  H264 headerheader[0] = 0;header[1] = 0;header[2] = 0;header[3] = 1;write(ffd, header, 4);}else{unsigned char header[6] = "\0"; //  AMR headerheader[0] = 0x23;header[1] = 0x21;header[2] = 0x41;header[3] = 0x4D;header[4] = 0x52;header[5] = 0x0A;write(ffd, header, sizeof(header));}
}//这个方法主要实现了AAC RTP音频流的存储,将存储的文件拷贝出来后,能正常播放
QTSS_Error  RTPSessionSaveOutput::WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr, Bool16 firstPacket)
{if (inPacket == NULL || inPacket->Len < 14){qtss_printf("inPacket == NULL || inPacket->Len < 14");return QTSS_NoErr;}SInt64                  currentTime = OS::Milliseconds();if(-1 != ffd){UInt8* payload = (UInt8*)inPacket->Ptr;int start = 12; //rtp header length//just save aac audio.if (inPacket->Len < 16){return QTSS_NoErr;}if (payload[start] != 0x00 || payload[start + 1] != 0x10){return QTSS_NoErr;}//check end.int rtptype = payload[start] & 0x1f;//int payload_type = payload[1]& 0x7f;bool bStartFlg = ((payload[start+1] & 0x80) == 0x80) ? true : false;//qtss_printf("#################   rtptype  = %d \n", rtptype);#if 0//这里用来做H264文件的存储if (rtptype== 28) { // FU-A//printf("################# rtptype== 28, arrivalTimeMSecPtr=%ld, len =%d\n", *arrivalTimeMSecPtr, inPacket->Len - start -2);if(bStartFlg) {Write_Header();}int ret = write(ffd, (void*)(payload + start + 2), (inPacket->Len - start -2));if(ret == -1){qtss_printf("error=%d, strerror=%s", errno, strerror(errno));}               } else if (rtptype <= 24){// single nal unit// printf("####################### rtptype rtptype =%d, arrivalTimeMSecPtr=%ld, len =%d\n", rtptype ,*arrivalTimeMSecPtr,  (inPacket->Len - start));//Write_Header();int ret = write(ffd, (void*)(payload+start), (inPacket->Len - start));// 00 00 00 01 if(ret == -1){qtss_printf("error=%d, strerror=%s", errno, strerror(errno));}}#elseif (lastArrivalTimeMSec >= *arrivalTimeMSecPtr){return QTSS_NoErr;}qtss_printf("RTPSessionSaveAsMP4::WritePacket lastArrivalTimeMSec=%d\n", lastArrivalTimeMSec);lastArrivalTimeMSec = *arrivalTimeMSecPtr;#if 1        //aac headerunsigned char adtsHeader[7] = {0};adtsHeader[0] = 0xFF;adtsHeader[1] = 0xF1;int profile = 2;int freqIdx = 11;int chanCfg = 1; int packetLen = inPacket->Len - start + 7 - 4;adtsHeader[2] = ((profile -1 )<<6) + (freqIdx << 2) + (chanCfg >> 2);adtsHeader[3] = ((chanCfg & 3) << 6) + (packetLen >> 11);adtsHeader[4] = (packetLen & 0x7ff) >> 3;//(packetLen >> 3) & 0xff;adtsHeader[5] = ((packetLen & 0x7) << 5)|0x1f;adtsHeader[6] = 0xFC;int ret = write(ffd, adtsHeader, sizeof(adtsHeader));
#endif//     //amr header//aaclatm 4headerret = write(ffd, (void*)(payload + start + 4), (inPacket->Len - start - 4));if(ret == -1){qtss_printf("error=%d, strerror=%s", errno, strerror(errno));}fLastIntervalMilliSec = currentTime - fLastPacketTransmitTime;if (fLastIntervalMilliSec > 100) {//reset interval maybe first packet or it has been blocked for awhilefLastIntervalMilliSec = 5;}fLastPacketTransmitTime = currentTime;if (!bLastBlockFlg){bLastBlockFlg = true;}else{bLastBlockFlg = false;}#endif//flush(ffd);}return QTSS_NoErr;
}



在完成文件输出的RTPSessionSaveOutput类后,我们修改QTSSReflectorModule中的,在推流的逻辑中,增加一个OutPut对象到会话中:

    // If this is an incoming data session, skip everything having to do with setting up a new// RTP Stream. if (isPush){//...//这里暂时只存储音频流if(theTrackID == 0) // qtssVideoPayloadType{RTPSessionSaveOutput* theSaveAsMP4 = NEW RTPSessionSaveOutput(theSession, theFullPath.Ptr);	         theSession->AddOutput(theSaveOutput, false); }}


这样就完成了推送音频流的存储,视频流的存储也能这样来实现,如果考虑音视频同时存储,可能要考虑其他方案来实现,比方需要通过ffmpeg来完成声音和视频融合成一个文件。


这篇关于Darwin推流存储实现介绍 之二的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/944848

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja