刚开始接触live555,在live555\testProgs中有很多很好的例子来讲解live555各方面的应用;
但是都是以文件形式给到服务器中,然后广播出来的,但是很多情况下我们是需要直播的,那就不能用文件的形式来操作了,
也试过用命名管道的方式,在linux上是可以的,但是在安卓上兼容性就不是很好了,像权限问题,以及命名管道不能在某种格式的内存中使用等,
所以本文就是描述怎样实现将已经成流的H264数据发送到网络(根据示例testH264VideoStreamer.cpp修改)。
核心部分是对类:FramedFileSource的继承;MyByteStreamFileSource
流程:
- 一个线程将H264数据从文件中读取出来放到缓存
- live555服务器不停地从缓存中获取数据
- live555广播出去
- 本地缓存需要的数据结构:
#include <pthread.h> #include <semaphore.h> #include <string.h> #include <sys/types.h> #include <stdlib.h> #include <stdio.h> #define STATIC_MEMORY #define FRAME_MEM_SIZE (1920*1080*2) #define MAX_QUENE_LENGTH 10 typedef struct NODE{ void *msg; unsigned int msgsize; struct NODE *next; }Node_t,*pNode_t; typedef struct{ //depend on Linux OS pthread_mutex_t qlock; sem_t sem; int q_num; pNode_t q_head; }Quene_t,*pQuene_t; static Quene_t ListData; static unsigned char availibleNode[MAX_QUENE_LENGTH]={0,0,0,0,0,0,0,0,0,0}; static Node_t nodeMemStore[MAX_QUENE_LENGTH]; static unsigned char availibleFrame[MAX_QUENE_LENGTH]={0,0,0,0,0,0,0,0,0,0}; static char frameMemStore[MAX_QUENE_LENGTH][FRAME_MEM_SIZE];
- 缓存申请和释放:
static void *mallocNode(void) { unsigned char i = 0; for(i=0;i<MAX_QUENE_LENGTH;i++) { if(availibleNode[i] == 0) { availibleNode[i] = 1; return &nodeMemStore[i]; } } return NULL; } static void freeNode(pNode_t node) { unsigned char i = 0; for(i=0;i<MAX_QUENE_LENGTH;i++) { if(&nodeMemStore[i] == node) availibleNode[i] = 0; } } static void *mallocFrame(void) { unsigned char i = 0; for(i=0;i<MAX_QUENE_LENGTH;i++) { if(availibleFrame[i] == 0) { availibleFrame[i] = 1; return &frameMemStore[i]; } } return NULL; } static void freeFrame(void* frame) { unsigned char i = 0; for(i=0;i<MAX_QUENE_LENGTH;i++) { if(&frameMemStore[i] == frame) availibleFrame[i] = 0; } }
- 缓存中数据的出入:
static int PutData(void * msg , unsigned int * size) { int ret = -1; dataLock(); if(ListData.q_num < MAX_QUENE_LENGTH) { #ifndef STATIC_MEMORY pNode_t node = malloc(sizeof(Node_t)); #else pNode_t node = (pNode_t)mallocNode(); #endif if(node == NULL) { ret = -1; } else { #ifndef STATIC_MEMORY node->msg = malloc(*size); #else node->msg = mallocFrame(); #endif if(node->msg == NULL) { #ifndef STATIC_MEMORY free(node); #else freeNode(node); #endif ret = -1; } else { int i = ListData.q_num; pNode_t n = ListData.q_head; memcpy(node->msg,msg,*size); node->msgsize = *size; node->next = NULL; if(i == 0) { ListData.q_head = node; } else { while(i-1) { n = n->next; i--; } n->next = node; } ListData.q_num += 1; ret = 0; } } } dataUnlock(); //emitDataSignal(); return ret; } static int RemoveData(void) { int ret = -1; dataLock(); if(0 == ListData.q_num) ret = -1; else { pNode_t n = ListData.q_head; ListData.q_head = ListData.q_head->next; ListData.q_num -= 1; #ifndef STATIC_MEMORY free(n->msg); free(n); #else freeFrame(n->msg); n->msgsize = 0; freeNode(n); #endif ret = 0; } dataUnlock(); return ret; } static unsigned int GetData(unsigned char * framebuf , unsigned int size) { unsigned int ret = 0; dataLock(); if(ListData.q_num == 0) { ret = 0; dataUnlock(); return ret; } else { if(size < ListData.q_head->msgsize) { dataUnlock(); printf("error:%s line:%d %d %d\n",__func__,__LINE__,size,ListData.q_head->msgsize); RemoveData(); return ret; } memcpy(framebuf,ListData.q_head->msg,ListData.q_head->msgsize); ret = ListData.q_head->msgsize; dataUnlock(); RemoveData(); return ret; } }
- 初始化缓存池:
//depend on Linux OS static int InitDataList(void) { if(pthread_mutex_init(&ListData.qlock,NULL) != 0) return -1; memset(availibleNode,0,sizeof(availibleNode)); memset(nodeMemStore,0,sizeof(nodeMemStore)); memset(availibleFrame,0,sizeof(availibleFrame)); memset(frameMemStore,0,sizeof(frameMemStore)); return 0; }
- 缓存池同步操作:
//depend on Linux OS static int dataLock(void) { return pthread_mutex_lock(&ListData.qlock); } //depend on Linux OS static int dataUnlock(void) { return pthread_mutex_unlock(&ListData.qlock); }
- 获取缓存中下一个数据的大小:
static unsigned int GetNextFrameSize(void) { unsigned int szie = 0; dataLock(); if(ListData.q_num != 0) szie = ListData.q_head->msgsize; dataUnlock(); return szie; }
- live555服务器需要的资源结构:
#include <liveMedia.hh> #include <BasicUsageEnvironment.hh> #include <GroupsockHelper.hh> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> typedef enum{ server_idle = 0, server_inited, server_pause }service_status_t; typedef struct{ service_status_t inited; UsageEnvironment* env; TaskScheduler* scheduler; unsigned short rtpPortNum; unsigned short rtcpPortNum; unsigned char ttl; Port *rtpPort; Port *rtcpPort; Groupsock *rtpGroupsock; Groupsock *rtcpGroupsock; H264VideoStreamFramer* videoSource; RTPSink* videoSink; RTCPInstance* rtcp; RTSPServer* rtspServer; ServerMediaSession* sms; PassiveServerMediaSubsession *passiveSubsession; MyByteStreamFileSource* fileSource; pthread_t putdata_pid; pthread_t loop_pid; }LocalServer_t; #define SIZES 2048 #define BANDWIDTH (512) #define MAX_CNAME_LEN 100 static const char *scrpath = "test1.h264"; static FILE* fd = NULL; static unsigned char running = 0; static unsigned char CNAME[MAX_CNAME_LEN+1]; static LocalServer_t SeverSetting = { .inited = server_idle, .env = NULL, .scheduler = NULL, .rtpPortNum = 0, .rtcpPortNum = 0, .ttl = 0, .rtpPort = NULL, .rtcpPort = NULL, .rtpGroupsock = NULL, .rtcpGroupsock = NULL, .videoSource = NULL, .videoSink = NULL, .rtcp = NULL, .rtspServer = NULL, .sms = NULL, .passiveSubsession = NULL, .fileSource = NULL, .putdata_pid = 0, .loop_pid = 0, };
- live555服务器的注册和开启:
static int start_play(void) { // Open the input file as a 'byte-stream file source': SeverSetting.fileSource = MyByteStreamFileSource::createNew(*SeverSetting.env, NULL); if(!SeverSetting.fileSource) { //*SeverSetting.env << "Unable to open file \"" << inputFileName<< "\" as a byte-stream file source\n"; return -1; } // Create a framer for the Video Elementary Stream: SeverSetting.videoSource = H264VideoStreamFramer::createNew(*SeverSetting.env,(FramedSource*)SeverSetting.fileSource); if(!SeverSetting.videoSource) { printf("%s %d\r\n",__func__,__LINE__); return -1; } // Finally, start playing: *SeverSetting.env << "start playing..."<<__LINE__<<"\n"; SeverSetting.videoSink->startPlaying(*SeverSetting.videoSource, NULL, NULL); return 0; } int StartRtspServer(char *url_suffix , int port) { printf("%s %d[%d]\r\n",__func__,__LINE__,SeverSetting.inited); if(SeverSetting.inited == server_pause) { char* url = SeverSetting.rtspServer->rtspURL(SeverSetting.sms); *SeverSetting.env << "Play this stream using the URL \"" << url << "\"\n"; delete[] url; *SeverSetting.env << "start playing..."<<__LINE__<<"\n"; //SeverSetting.videoSink->startPlaying(*SeverSetting.videoSource, NULL, NULL); SeverSetting.inited = server_inited; return 0; } else if(SeverSetting.inited == server_inited) return -1; if(InitDataList()) { printf("InitDataLock fail\n"); return -1; } SeverSetting.scheduler = BasicTaskScheduler::createNew(); if(!SeverSetting.scheduler) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.env = BasicUsageEnvironment::createNew(*SeverSetting.scheduler); if(!SeverSetting.env) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.rtpPortNum = 18888; SeverSetting.rtcpPortNum = SeverSetting.rtpPortNum+1; SeverSetting.ttl = 255; SeverSetting.rtpPort = new Port(SeverSetting.rtpPortNum); if(!SeverSetting.rtpPort) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.rtcpPort = new Port(SeverSetting.rtcpPortNum); if(!SeverSetting.rtcpPort) { printf("%s %d\r\n",__func__,__LINE__); return -1; } struct in_addr destinationAddress; destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*SeverSetting.env); SeverSetting.rtpGroupsock = new Groupsock(*SeverSetting.env,destinationAddress, *SeverSetting.rtpPort,SeverSetting.ttl); if(!SeverSetting.rtpGroupsock) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.rtpGroupsock->multicastSendOnly(); // we're a SSM source SeverSetting.rtcpGroupsock = new Groupsock(*SeverSetting.env,destinationAddress, *SeverSetting.rtcpPort,SeverSetting.ttl); if(!SeverSetting.rtcpGroupsock) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.rtcpGroupsock->multicastSendOnly(); // we're a SSM source // Create a 'H264 Video RTP' sink from the RTP 'groupsock': OutPacketBuffer::maxSize = 1024*1024; SeverSetting.videoSink = H264VideoRTPSink::createNew(*SeverSetting.env, SeverSetting.rtpGroupsock,96); if(!SeverSetting.videoSink) { printf("%s %d\r\n",__func__,__LINE__); return -1; } // Create (and start) a 'RTCP instance' for this RTP sink: memset(CNAME,0,MAX_CNAME_LEN); gethostname((char*)CNAME, MAX_CNAME_LEN); CNAME[MAX_CNAME_LEN] = '\0'; // just in case SeverSetting.rtcp = RTCPInstance::createNew(*SeverSetting.env,SeverSetting.rtcpGroupsock, BANDWIDTH,// in kbps; for RTCP b/w share CNAME, SeverSetting.videoSink, NULL /* we're a server */, True /* we're a SSM source */); if(!SeverSetting.rtcp) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.rtspServer = RTSPServer::createNew(*SeverSetting.env, port); if(SeverSetting.rtspServer == NULL) { *SeverSetting.env << "Failed to create RTSP server: " << SeverSetting.env->getResultMsg() << "\n"; return -1; } SeverSetting.sms = ServerMediaSession::createNew(*SeverSetting.env,url_suffix,NULL, "Session streamed by \"testH264VideoStreamer\"",True); if(!SeverSetting.sms) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.passiveSubsession = PassiveServerMediaSubsession::createNew(*SeverSetting.videoSink,SeverSetting.rtcp); if(!SeverSetting.passiveSubsession) { printf("%s %d\r\n",__func__,__LINE__); return -1; } SeverSetting.sms->addSubsession(SeverSetting.passiveSubsession); SeverSetting.rtspServer->addServerMediaSession(SeverSetting.sms); char* url = SeverSetting.rtspServer->rtspURL(SeverSetting.sms); *SeverSetting.env << "Play this stream using the URL \"" << url << "\"\n"; delete[] url; // Start the streaming: *SeverSetting.env << "Beginning streaming...\n"; if(start_play()) { printf("%s %d\r\n",__func__,__LINE__); return -1; } run_tasks(); SeverSetting.inited = server_inited; return 0; }
- 读取数据放到缓存中:
int InsertFrame(char * buf ,int len) { if(SeverSetting.inited == server_inited) return PutData((void *)buf, (unsigned int*)&len); else return -1; } static void* receive_thread(void* param) { unsigned int len = 0; unsigned char buf[FRAME_MEM_SIZE]; printf("%s %d\r\n",__func__,__LINE__); fd = fopen(scrpath,"rb"); if(fd) printf("open ok\n"); else { printf("open \"%s\" fail\n",scrpath); _exit(1); } printf("%s %d\n",__func__,__LINE__); running = 1; while(running) { if((len=fread(buf,1,SIZES,fd))>0) { f_try: if(InsertFrame((char*)buf,(int)len) < 0) { usleep(100000); goto f_try; } } if(len < SIZES || len == 0) fseek(fd,0,SEEK_SET); } fclose(fd); printf("%s %d\n",__func__,__LINE__); pthread_exit(NULL); return NULL; } static void* loop_thread(void* param) { printf("task schedule start...[%d]\n",__LINE__); SeverSetting.env->taskScheduler().doEventLoop(); printf("task schedule start...[%d]\n",__LINE__); pthread_exit(NULL); return NULL; } static void run_tasks(void) { pthread_create(&SeverSetting.putdata_pid,NULL,receive_thread,NULL); pthread_detach(SeverSetting.putdata_pid); pthread_create(&SeverSetting.loop_pid,NULL,loop_thread,NULL); pthread_detach(SeverSetting.loop_pid); }
- 停止服务器:
void StopRtspServer(void) { printf("%s relese\n",__func__); if(SeverSetting.inited == server_inited) SeverSetting.inited = server_pause; //SeverSetting.videoSink->stopPlaying(); //Medium::close(SeverSetting.videoSource); printf("%s relese all\n",__func__); }
- 主函数入口:
int main(int argc , char *argv[]) { StartRtspServer((char*)"xxx",8554); while(1) { sleep(10); } return 0; }
- 主要部分:对FramedFileSource的继承
class MyByteStreamFileSource: public FramedFileSource { public: static MyByteStreamFileSource* createNew(UsageEnvironment& env, char const* fileName, unsigned preferredFrameSize = 0, unsigned playTimePerFrame = 0) { MyByteStreamFileSource* newSource = new MyByteStreamFileSource(env, 0, preferredFrameSize, playTimePerFrame); newSource->fFileSize = 0;//GetFileSize(fileName, fid); printf(">fFileSize %d<:%llu\n",__LINE__,newSource->fFileSize); return newSource; } u_int64_t fileSize() const { return fFileSize; } // 0 means zero-length, unbounded, or unknown void seekToByteAbsolute(u_int64_t byteNumber, u_int64_t numBytesToStream = 0) { printf("%s %d\n",__func__,__LINE__); fNumBytesToStream = numBytesToStream; fLimitNumBytesToStream = fNumBytesToStream > 0; } // if "numBytesToStream" is >0, then we limit the stream to that number of bytes, before treating it as EOF void seekToByteRelative(int64_t offset, u_int64_t numBytesToStream = 0) { printf("%s %d\n",__func__,__LINE__); fNumBytesToStream = numBytesToStream; fLimitNumBytesToStream = fNumBytesToStream > 0; } void seekToEnd()// to force EOF handling on the next read { printf("%s %d\n",__func__,__LINE__); } protected: MyByteStreamFileSource(UsageEnvironment& env, FILE* fid, unsigned preferredFrameSize, unsigned playTimePerFrame) : FramedFileSource(env, fid), fFileSize(0), fPreferredFrameSize(preferredFrameSize), fPlayTimePerFrame(playTimePerFrame), fLastPlayTime(0), fHaveStartedReading(False), fLimitNumBytesToStream(False), fNumBytesToStream(0) { printf(">%d<\n",__LINE__); overFlowNum = 0; overFlowIndex = 0; // Test whether the file is seekable fFidIsSeekable = 0; printf(">fFidIsSeekable %d<:%d\n",__LINE__,fFidIsSeekable); } virtual ~MyByteStreamFileSource() { if (fFid == NULL) return; CloseInputFile(fFid); } static void fileReadableHandler(void* source) { MyByteStreamFileSource* pThis = (MyByteStreamFileSource*)source; if (!pThis->isCurrentlyAwaitingData()) { pThis->doStopGettingFrames(); // we're not ready for the data yet return; } pThis->doReadFromFile(); } void doReadFromFile() { //printf("%s %d\n",__func__,__LINE__); // Try to read as many bytes as will fit in the buffer provided (or "fPreferredFrameSize" if less) if (fLimitNumBytesToStream && fNumBytesToStream < (u_int64_t)fMaxSize) { printf("fNumBytesToStream:%llu fMaxSize:%u fPreferredFrameSize:%u\n", fNumBytesToStream,fMaxSize,fPreferredFrameSize); fMaxSize = (unsigned)fNumBytesToStream; } if (fPreferredFrameSize > 0 && fPreferredFrameSize < fMaxSize) { printf("fPreferredFrameSize:%llu fMaxSize:%u fPreferredFrameSize:%u\n", fNumBytesToStream,fMaxSize,fPreferredFrameSize); fMaxSize = fPreferredFrameSize; } f_again: if(overFlowNum) { if(overFlowNum - overFlowIndex > fMaxSize) { memcpy(fTo,&overFlow[overFlowIndex],fMaxSize); fFrameSize = fMaxSize; overFlowIndex += fFrameSize; printf("%s %d>\n",__func__,__LINE__); } else { memcpy(fTo,&overFlow[overFlowIndex],overFlowNum - overFlowIndex); fFrameSize = overFlowNum - overFlowIndex; overFlowIndex += fFrameSize; printf("%s %d<\n",__func__,__LINE__); } if(overFlowNum == overFlowIndex) { overFlowNum = 0; overFlowIndex = 0; printf("%s %d reset\n",__func__,__LINE__); } } else if(overFlowNum == 0 && GetNextFrameSize() > fMaxSize) { if((overFlowNum=GetData(overFlow,FRAME_MEM_SIZE)) > 0) { printf("%s %d max\n",__func__,__LINE__); memcpy(fTo,overFlow,fMaxSize); fFrameSize = fMaxSize; overFlowIndex = fMaxSize; if(overFlowNum == overFlowIndex) { overFlowNum = 0; overFlowIndex = 0; } } else { printf("%s sleep %d\n",__func__,__LINE__); usleep(50000); goto f_again; } } else if((fFrameSize = GetData(fTo,fMaxSize)) == 0) { printf("%s no src data %d\n",__func__,__LINE__); usleep(50000); goto f_again; } if (fFrameSize == 0) { handleClosure(); return; } fNumBytesToStream -= fFrameSize; // Set the 'presentation time': if (fPlayTimePerFrame > 0 && fPreferredFrameSize > 0) { if (fPresentationTime.tv_sec == 0 && fPresentationTime.tv_usec == 0) { // This is the first frame, so use the current time: gettimeofday(&fPresentationTime, NULL); } else { // Increment by the play time of the previous data: unsigned uSeconds = fPresentationTime.tv_usec + fLastPlayTime; fPresentationTime.tv_sec += uSeconds/1000000; fPresentationTime.tv_usec = uSeconds%1000000; } // Remember the play time of this data: fLastPlayTime = (fPlayTimePerFrame*fFrameSize)/fPreferredFrameSize; fDurationInMicroseconds = fLastPlayTime; } else { // We don't know a specific play time duration for this data, // so just record the current time as being the 'presentation time': gettimeofday(&fPresentationTime, NULL); } // Because the file read was done from the event loop, we can call the // 'after getting' function directly, without risk of infinite recursion: FramedSource::afterGetting(this); } private: // redefined virtual functions: virtual void doGetNextFrame() { //printf("%s %d\n",__func__,__LINE__); envir().taskScheduler().scheduleDelayedTask(0,fileReadableHandler, this); } virtual void doStopGettingFrames() { //printf("%s %d\n",__func__,__LINE__); envir().taskScheduler().unscheduleDelayedTask(nextTask()); } protected: u_int64_t fFileSize; u_int64_t overFlowNum; u_int64_t overFlowIndex; unsigned char overFlow[FRAME_MEM_SIZE]; private: unsigned fPreferredFrameSize; unsigned fPlayTimePerFrame; Boolean fFidIsSeekable; unsigned fLastPlayTime; Boolean fHaveStartedReading; Boolean fLimitNumBytesToStream; u_int64_t fNumBytesToStream; // used iff "fLimitNumBytesToStream" is True };
值得注意的是:在类MyByteStreamFileSource中doReadFromFile的时候,往数据区fTo中拷贝数据的时候,当前一帧数据可能会超过fMaxSize的大小,如果往fTo中考入数据超过fMaxSize的大小会导致live555报错,所以我们在这里采取一个中间缓存,当fMaxSize的大小小于下一帧的大小的时候,将下一帧的数据先考到中间缓存中,再从中间缓存中拷贝fMaxSize大小的数据到fTo中,下次再将缓存中的其他数据放入到fTo中;
此处有源码下载:http://download.youkuaiyun.com/download/xushan239/10221824