Live555学习之(六)---------- 在Live555中实现录像

  Live555还提供了录像的示例程序,在testProgs目录下的playCommon.cpp中,Live555录像的基本原理就是创建一个RTSPClient去请求指定rtsp地址的视频,然后保存到文件里。

  playCommon.cpp打开一看就发现首先是各种全局函数的声明,然后是各种全局变量的声明,然后是main函数和各个函数的实现。main函数中首先还是创建TaskScheduler对象和UsageEnvironment对象,然后根据各种输入参数设置各种全局变量,最后就是创建一个RTSPClient对象请求指定rtsp地址的视频。

 1 int main(int argc, char** argv) 
 2 {
 3   // Begin by setting up our usage environment:
 4   TaskScheduler* scheduler = BasicTaskScheduler::createNew();
 5   env = BasicUsageEnvironment::createNew(*scheduler);
 6   
 7    /*
 8         处理各种输入参数,在此省略
 9    */
10   streamURL = argv[1];
11 
12   // Create (or arrange to create) our client object:
13   if (createHandlerServerForREGISTERCommand) {
14     handlerServerForREGISTERCommand
15       = HandlerServerForREGISTERCommand::createNew(*env, continueAfterClientCreation0,
16                            handlerServerForREGISTERCommandPortNum, authDBForREGISTER,
17                            verbosityLevel, progName);
18     if (handlerServerForREGISTERCommand == NULL) {
19       *env << "Failed to create a server for handling incoming \"REGISTER\" commands: " << env->getResultMsg() << "\n";
20     } else {
21       *env << "Awaiting an incoming \"REGISTER\" command on port " << handlerServerForREGISTERCommand->serverPortNum() << "\n";
22     }
23   } else {
24     ourClient = createClient(*env, streamURL, verbosityLevel, progName);
25     if (ourClient == NULL) {
26       *env << "Failed to create " << clientProtocolName << " client: " << env->getResultMsg() << "\n";
27       shutdown();
28     }
29     continueAfterClientCreation1();
30   }
31 
32   // All subsequent activity takes place within the event loop:
33   env->taskScheduler().doEventLoop(); // does not return
34 
35   return 0; // only to prevent compiler warning
36 }
37   

   createClient函数在openRTSP.cpp文件中定义,内容很简单,就是调用了RTSPClient::createNew函数创建了一个RTSPClient对象。我们来看continueAfterClientCreation1函数

  1 void continueAfterClientCreation1() {
  2   setUserAgentString(userAgent);
  3 
  4   if (sendOptionsRequest) {
  5     // Begin by sending an "OPTIONS" command:
  6     getOptions(continueAfterOPTIONS);                  // 发送OPTIONS命令,我们也可以跳过这一步直接发送DESCRIBE命令
  7   } else {
  8     continueAfterOPTIONS(NULL, 0, NULL);
  9   }
 10 }
 11 
 12 void getOptions(RTSPClient::responseHandler* afterFunc) { 
 13   ourRTSPClient->sendOptionsCommand(afterFunc, ourAuthenticator);
 14 }
 15 
 16 void continueAfterOPTIONS(RTSPClient*, int resultCode, char* resultString) {
 17   if (sendOptionsRequestOnly) {
 18     if (resultCode != 0) {
 19       *env << clientProtocolName << " \"OPTIONS\" request failed: " << resultString << "\n";
 20     } else {
 21       *env << clientProtocolName << " \"OPTIONS\" request returned: " << resultString << "\n";
 22     }
 23     shutdown();
 24   }
 25   delete[] resultString;
 26 
 27   // Next, get a SDP description for the stream:
 28   getSDPDescription(continueAfterDESCRIBE);                     // 发送DESCRIBE命令
 29 }
 30 
 31 void getSDPDescription(RTSPClient::responseHandler* afterFunc) {
 32   ourRTSPClient->sendDescribeCommand(afterFunc, ourAuthenticator);
 33 }
 34 
 35 void continueAfterDESCRIBE(RTSPClient*, int resultCode, char* resultString) {
 36   if (resultCode != 0) {
 37     *env << "Failed to get a SDP description for the URL \"" << streamURL << "\": " << resultString << "\n";
 38     delete[] resultString;
 39     shutdown();
 40   }
 41 
 42   char* sdpDescription = resultString;
 43   *env << "Opened URL \"" << streamURL << "\", returning a SDP description:\n" << sdpDescription << "\n";
 44 
 45   // Create a media session object from this SDP description:
 46   session = MediaSession::createNew(*env, sdpDescription);          //创建MediaSession
 47   delete[] sdpDescription;
 48   if (session == NULL) {
 49     *env << "Failed to create a MediaSession object from the SDP description: " << env->getResultMsg() << "\n";
 50     shutdown();
 51   } else if (!session->hasSubsessions()) {
 52     *env << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
 53     shutdown();
 54   }
 55 
 56   // Then, setup the "RTPSource"s for the session:            
 57   MediaSubsessionIterator iter(*session);
 58   MediaSubsession *subsession;
 59   Boolean madeProgress = False;
 60   char const* singleMediumToTest = singleMedium;
 61   while ((subsession = iter.next()) != NULL) {
 62     // If we've asked to receive only a single medium, then check this now:
 63     if (singleMediumToTest != NULL) {
 64       if (strcmp(subsession->mediumName(), singleMediumToTest) != 0) {
 65           *env << "Ignoring \"" << subsession->mediumName()
 66               << "/" << subsession->codecName()
 67               << "\" subsession, because we've asked to receive a single " << singleMedium
 68               << " session only\n";
 69     continue;
 70       } else {
 71     // Receive this subsession only
 72     singleMediumToTest = "xxxxx";
 73         // this hack ensures that we get only 1 subsession of this type
 74       }
 75     }
 76 
 77     if (desiredPortNum != 0) {
 78       subsession->setClientPortNum(desiredPortNum);                       //创建相关的RTPSource、Groupsock等资源
 79       desiredPortNum += 2;
 80     }
 81 
 82     if (createReceivers) {                                                  //我们接收数据然后保存在文件中,createReceivers为true
 83       if (!subsession->initiate(simpleRTPoffsetArg)) {                      //初始化MediaSubsession
 84     *env << "Unable to create receiver for \"" << subsession->mediumName()
 85          << "/" << subsession->codecName()
 86          << "\" subsession: " << env->getResultMsg() << "\n";
 87       } else {
 88     *env << "Created receiver for \"" << subsession->mediumName()
 89          << "/" << subsession->codecName() << "\" subsession (";
 90     if (subsession->rtcpIsMuxed()) {
 91       *env << "client port " << subsession->clientPortNum();
 92     } else {
 93       *env << "client ports " << subsession->clientPortNum()
 94            << "-" << subsession->clientPortNum()+1;
 95     }
 96     *env << ")\n";
 97     madeProgress = True;
 98     
 99     if (subsession->rtpSource() != NULL) {
100       // Because we're saving the incoming data, rather than playing
101       // it in real time, allow an especially large time threshold
102       // (1 second) for reordering misordered incoming packets:
103       unsigned const thresh = 1000000; // 1 second
104       subsession->rtpSource()->setPacketReorderingThresholdTime(thresh);
105       
106       // Set the RTP source's OS socket buffer size as appropriate - either if we were explicitly asked (using -B),
107       // or if the desired FileSink buffer size happens to be larger than the current OS socket buffer size.
108       // (The latter case is a heuristic, on the assumption that if the user asked for a large FileSink buffer size,
109       // then the input data rate may be large enough to justify increasing the OS socket buffer size also.)
110       int socketNum = subsession->rtpSource()->RTPgs()->socketNum();
111       unsigned curBufferSize = getReceiveBufferSize(*env, socketNum);
112       if (socketInputBufferSize > 0 || fileSinkBufferSize > curBufferSize) {
113         unsigned newBufferSize = socketInputBufferSize > 0 ? socketInputBufferSize : fileSinkBufferSize;
114         newBufferSize = setReceiveBufferTo(*env, socketNum, newBufferSize);
115         if (socketInputBufferSize > 0) { // The user explicitly asked for the new socket buffer size; announce it:
116           *env << "Changed socket receive buffer size for the \""
117            << subsession->mediumName()
118            << "/" << subsession->codecName()
119            << "\" subsession from "
120            << curBufferSize << " to "
121            << newBufferSize << " bytes\n";
122         }
123       }
124     }
125       }
126     } else {
127       if (subsession->clientPortNum() == 0) {
128     *env << "No client port was specified for the \""
129          << subsession->mediumName()
130          << "/" << subsession->codecName()
131          << "\" subsession.  (Try adding the \"-p <portNum>\" option.)\n";
132       } else {
133         madeProgress = True;
134       }
135     }
136   }
137   if (!madeProgress) shutdown();
138 
139   // Perform additional 'setup' on each subsession, before playing them:
140   setupStreams();                                    //对每个ServerMediaSubsession发送SETUP命令
141 }

  上面的流程和RTSPClient端与服务器建立连接的过程基本类似,先发送OPTIONS命令,然后发送DESCRIBE命令,然后发送SETUP命令。我们在此也可以忽略发送OPTIONS命令,直接从发送DESCRIBE命令开始。在setupStreams函数中分别对每个ServerMediaSubsession发送SETUP命令,我们来看一下setupStreams函数

 1 void setupStreams() {
 2   static MediaSubsessionIterator* setupIter = NULL;
 3   if (setupIter == NULL) setupIter = new MediaSubsessionIterator(*session);
 4   while ((subsession = setupIter->next()) != NULL) {
 5     // We have another subsession left to set up:
 6     if (subsession->clientPortNum() == 0) continue; // port # was not set
 7 
 8     setupSubsession(subsession, streamUsingTCP, forceMulticastOnUnspecified, continueAfterSETUP);  //发送SETUP命令,建立与ServerMediaSubsession的连接
 9     return;
10   }
11 
12   // We're done setting up subsessions.  //与所有的ServerMediaSubsession建立连接成功
13   delete setupIter;
14   if (!madeProgress) shutdown();
15 
16   // Create output files:
17   if (createReceivers) {
18     if (fileOutputInterval > 0) {
19       createPeriodicOutputFiles();    //创建周期性的输出文件,例如:我们可以设置每一个小时输出一个录像文件
20     } else {
21       createOutputFiles("");
22     }
23   }
24 
25   // Finally, start playing each subsession, to start the data flow:
26   if (duration == 0) {
27     if (scale > 0) duration = session->playEndTime() - initialSeekTime; // use SDP end time
28     else if (scale < 0) duration = initialSeekTime;
29   }
30   if (duration < 0) duration = 0.0;
31 
32   endTime = initialSeekTime;
33   if (scale > 0) {
34     if (duration <= 0) endTime = -1.0f;
35     else endTime = initialSeekTime + duration;
36   } else {
37     endTime = initialSeekTime - duration;
38     if (endTime < 0) endTime = 0.0f;
39   }
40                 // 发送PLAY命令请求开始播放视频
41   char const* absStartTime = initialAbsoluteSeekTime != NULL ? initialAbsoluteSeekTime : session->absStartTime();
42   if (absStartTime != NULL) {
43     // Either we or the server have specified that seeking should be done by 'absolute' time:
44     startPlayingSession(session, absStartTime, session->absEndTime(), scale, continueAfterPLAY);
45   } else {
46     // Normal case: Seek by relative time (NPT):
47     startPlayingSession(session, initialSeekTime, endTime, scale, continueAfterPLAY);        
48   }
49 }
50 
51 void setupSubsession(MediaSubsession* subsession, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified, RTSPClient::responseHandler* afterFunc) {
52   
53   ourRTSPClient->sendSetupCommand(*subsession, afterFunc, False, streamUsingTCP, forceMulticastOnUnspecified, ourAuthenticator);
54 }
55 
56 void startPlayingSession(MediaSession* session, double start, double end, float scale, RTSPClient::responseHandler* afterFunc) {
57   printf("\n\n\n%f - %f\n\n\n",start,end);
58   ourRTSPClient->sendPlayCommand(*session, afterFunc, start, end, scale, ourAuthenticator);
59 }

  在setupStreams函数中依次与每个ServerMediaSubsession建立连接,都建立连接成功后,然后就调用createPeriodicOutputFiles函数周期性地创建输出录像的文件,然后开始发送PLAY命令请求播放视频。接下来我们看看createPeriodicOutputFiles这个函数

  1 void createPeriodicOutputFiles() {
  2   // Create a filename suffix that notes the time interval that's being recorded:
  3   char periodicFileNameSuffix[100];
  4   snprintf(periodicFileNameSuffix, sizeof periodicFileNameSuffix, "-%05d-%05d",
  5        fileOutputSecondsSoFar, fileOutputSecondsSoFar + fileOutputInterval);
  6   createOutputFiles(periodicFileNameSuffix);              //创建输出文件
  7 
  8   // Schedule an event for writing the next output file:    //添加一个停止当前录像,创建新录像文件的任务
  9   periodicFileOutputTask
 10     = env->taskScheduler().scheduleDelayedTask(fileOutputInterval*1000000,
 11                            (TaskFunc*)periodicFileOutputTimerHandler,
 12                            (void*)NULL);
 13 }
 14 
 15 void createOutputFiles(char const* periodicFilenameSuffix) {
 16   char outFileName[1000];
 17 
// 创建对应的FileSink来获取和保存视频数据 18 if (outputQuickTimeFile || outputAVIFile) { 19 if (periodicFilenameSuffix[0] == '\0') { 20 // Normally (unless the '-P <interval-in-seconds>' option was given) we output to 'stdout': 21 sprintf(outFileName, "stdout"); 22 } else { 23 // Otherwise output to a type-specific file name, containing "periodicFilenameSuffix": 24 char const* prefix = fileNamePrefix[0] == '\0' ? "output" : fileNamePrefix; 25 snprintf(outFileName, sizeof outFileName, "%s%s.%s", prefix, periodicFilenameSuffix, 26 outputAVIFile ? "avi" : generateMP4Format ? "mp4" : "mov"); 27 } 28 29 if (outputQuickTimeFile) { 30 qtOut = QuickTimeFileSink::createNew(*env, *session, outFileName, 31 fileSinkBufferSize, 32 movieWidth, movieHeight, 33 movieFPS, 34 packetLossCompensate, 35 syncStreams, 36 generateHintTracks, 37 generateMP4Format); 38 if (qtOut == NULL) { 39 *env << "Failed to create a \"QuickTimeFileSink\" for outputting to \"" 40 << outFileName << "\": " << env->getResultMsg() << "\n"; 41 shutdown(); 42 } else { 43 *env << "Outputting to the file: \"" << outFileName << "\"\n"; 44 } 45 46 qtOut->startPlaying(sessionAfterPlaying, NULL); 47 } else { // outputAVIFile 48 aviOut = AVIFileSink::createNew(*env, *session, outFileName, 49 fileSinkBufferSize, 50 movieWidth, movieHeight, 51 movieFPS, 52 packetLossCompensate); 53 if (aviOut == NULL) { 54 *env << "Failed to create an \"AVIFileSink\" for outputting to \"" 55 << outFileName << "\": " << env->getResultMsg() << "\n"; 56 shutdown(); 57 } else { 58 *env << "Outputting to the file: \"" << outFileName << "\"\n"; 59 } 60 61 aviOut->startPlaying(sessionAfterPlaying, NULL); 62 } 63 } else { //我直接保存录像成.H264文件,所以执行此else分支 64 // Create and start "FileSink"s for each subsession: 65 madeProgress = False; 66 MediaSubsessionIterator iter(*session); 67 while ((subsession = iter.next()) != NULL) { 68 if (subsession->readSource() == NULL) continue; // was not initiated 69 70 // Create an output file for each desired stream: 71 if (singleMedium == NULL || periodicFilenameSuffix[0] != '\0') { 72 // Output file name is 73 // "<filename-prefix><medium_name>-<codec_name>-<counter><periodicFilenameSuffix>" 74 static unsigned streamCounter = 0; 75 snprintf(outFileName, sizeof outFileName, "%s%s-%s-%d%s", 76 fileNamePrefix, subsession->mediumName(), 77 subsession->codecName(), ++streamCounter, periodicFilenameSuffix); 78 } else { 79 // When outputting a single medium only, we output to 'stdout 80 // (unless the '-P <interval-in-seconds>' option was given): 81 sprintf(outFileName, "stdout"); 82 } 83 84 FileSink* fileSink = NULL; 85 Boolean createOggFileSink = False; // by default 86 if (strcmp(subsession->mediumName(), "video") == 0) { 87 if (strcmp(subsession->codecName(), "H264") == 0) { // 创建H264VideoFileSink来获取和保存H264视频数据 88 // For H.264 video stream, we use a special sink that adds 'start codes', 89 // and (at the start) the SPS and PPS NAL units: 90 fileSink = H264VideoFileSink::createNew(*env, outFileName, 91 subsession->fmtp_spropparametersets(), 92 fileSinkBufferSize, oneFilePerFrame); 93 } else if (strcmp(subsession->codecName(), "H265") == 0) { 94 // For H.265 video stream, we use a special sink that adds 'start codes', 95 // and (at the start) the VPS, SPS, and PPS NAL units: 96 fileSink = H265VideoFileSink::createNew(*env, outFileName, 97 subsession->fmtp_spropvps(), 98 subsession->fmtp_spropsps(), 99 subsession->fmtp_sproppps(), 100 fileSinkBufferSize, oneFilePerFrame); 101 } else if (strcmp(subsession->codecName(), "THEORA") == 0) { 102 createOggFileSink = True; 103 } 104 } else if (strcmp(subsession->mediumName(), "audio") == 0) { 105 if (strcmp(subsession->codecName(), "AMR") == 0 || 106 strcmp(subsession->codecName(), "AMR-WB") == 0) { 107 // For AMR audio streams, we use a special sink that inserts AMR frame hdrs: 108 fileSink = AMRAudioFileSink::createNew(*env, outFileName, 109 fileSinkBufferSize, oneFilePerFrame); 110 } else if (strcmp(subsession->codecName(), "VORBIS") == 0 || 111 strcmp(subsession->codecName(), "OPUS") == 0) { 112 createOggFileSink = True; 113 } 114 } 115 if (createOggFileSink) { 116 fileSink = OggFileSink 117 ::createNew(*env, outFileName, 118 subsession->rtpTimestampFrequency(), subsession->fmtp_config()); 119 } else if (fileSink == NULL) { 120 // Normal case:                              //不属于上面的各种情形,创建一个普通FileSink获取和保存数据 121 fileSink = FileSink::createNew(*env, outFileName, 122 fileSinkBufferSize, oneFilePerFrame); 123 } 124 subsession->sink = fileSink; 125 126 if (subsession->sink == NULL) { 127 *env << "Failed to create FileSink for \"" << outFileName 128 << "\": " << env->getResultMsg() << "\n"; 129 } else { 130 if (singleMedium == NULL) { 131 *env << "Created output file: \"" << outFileName << "\"\n"; 132 } else { 133 *env << "Outputting data from the \"" << subsession->mediumName() 134 << "/" << subsession->codecName() 135 << "\" subsession to \"" << outFileName << "\"\n"; 136 } 137 138 if (strcmp(subsession->mediumName(), "video") == 0 && 139 strcmp(subsession->codecName(), "MP4V-ES") == 0 && 140 subsession->fmtp_config() != NULL) { 141 // For MPEG-4 video RTP streams, the 'config' information 142 // from the SDP description contains useful VOL etc. headers. 143 // Insert this data at the front of the output file: 144 unsigned configLen; 145 unsigned char* configData 146 = parseGeneralConfigStr(subsession->fmtp_config(), configLen); 147 struct timeval timeNow; 148 gettimeofday(&timeNow, NULL); 149 fileSink->addData(configData, configLen, timeNow); 150 delete[] configData; 151 } 152 153 subsession->sink->startPlaying(*(subsession->readSource()), // 开始获取数据并保存 154 subsessionAfterPlaying, 155 subsession); 156 157 // Also set a handler to be called if a RTCP "BYE" arrives 158 // for this subsession: 159 if (subsession->rtcpInstance() != NULL) { 160 subsession->rtcpInstance()->setByeHandler(subsessionByeHandler, subsession); 161 } 162 163 madeProgress = True; 164 } 165 } 166 if (!madeProgress) shutdown(); 167 } 168 } 169 170 void periodicFileOutputTimerHandler(void* /*clientData*/) { //完成了一个录像文件,关闭相关资源,开始下一个录像文件 171 fileOutputSecondsSoFar += fileOutputInterval; 172 173 // First, close the existing output files: 174 closeMediaSinks(); 175 176 // Then, create new output files: 177 createPeriodicOutputFiles(); 178 } 179 180 void closeMediaSinks() { //关闭FileSink,释放资源 181 Medium::close(qtOut); qtOut = NULL; 182 Medium::close(aviOut); aviOut = NULL; 183 184 if (session == NULL) return; 185 MediaSubsessionIterator iter(*session); 186 MediaSubsession* subsession; 187 while ((subsession = iter.next()) != NULL) { 188 Medium::close(subsession->sink); 189 subsession->sink = NULL; 190 } 191 192 }

  首先创建一个录像文件,然后创建FileSink从服务器获取和保存录像数据,当指定的录像时长到了,就终止当前录像,开始下一个录像文件。如此,就可以实现每隔指定的一段时间录像成一个文件的功能。我这里是获取的H264编码的视频,创建的是H264VideoFileSink来获取和保存数据,H264VideoFileSink是H264or5VideoFileSink的子类,H264or5VideoFileSink又是FileSink的子类。FileSink通过MediaSubsession的FramedSource获取数据,然后保存到文件中,我们来看一下保存文件的过程。

 1 void H264or5VideoFileSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime) {
 2   unsigned char const start_code[4] = {0x00, 0x00, 0x00, 0x01};          //H264帧的开头是0x00000001
 3 
 4   if (!fHaveWrittenFirstFrame) {
 5     // If we have NAL units encoded in "sprop parameter strings", prepend these to the file:
 6     for (unsigned j = 0; j < 3; ++j) {
 7       unsigned numSPropRecords;
 8       SPropRecord* sPropRecords
 9     = parseSPropParameterSets(fSPropParameterSetsStr[j], numSPropRecords);
10       for (unsigned i = 0; i < numSPropRecords; ++i) {
11     addData(start_code, 4, presentationTime);
12     addData(sPropRecords[i].sPropBytes, sPropRecords[i].sPropLength, presentationTime);
13       }
14       delete[] sPropRecords;
15     }
16     fHaveWrittenFirstFrame = True; // for next time
17   }
18 
19   // Write the input data to the file, with the start code in front:
20   addData(start_code, 4, presentationTime);              //先把每一帧的头部写入文件             
21   //调用FileSink类的afterGettingFrame函数写入一帧的图像数据到文件
22   // Call the parent class to complete the normal file write with the input data:
23   FileSink::afterGettingFrame(frameSize, numTruncatedBytes, presentationTime);   24 }
25 
26 void FileSink::addData(unsigned char const* data, unsigned dataSize,
27                struct timeval presentationTime) {
28   if (fPerFrameFileNameBuffer != NULL && fOutFid == NULL) {
29     // Special case: Open a new file on-the-fly for this frame
30     if (presentationTime.tv_usec == fPrevPresentationTime.tv_usec &&
31     presentationTime.tv_sec == fPrevPresentationTime.tv_sec) {
32       // The presentation time is unchanged from the previous frame, so we add a 'counter'
33       // suffix to the file name, to distinguish them:
34       sprintf(fPerFrameFileNameBuffer, "%s-%lu.%06lu-%u", fPerFrameFileNamePrefix,
35           presentationTime.tv_sec, presentationTime.tv_usec, ++fSamePresentationTimeCounter);
36     } else {
37       sprintf(fPerFrameFileNameBuffer, "%s-%lu.%06lu", fPerFrameFileNamePrefix,
38           presentationTime.tv_sec, presentationTime.tv_usec);
39       fPrevPresentationTime = presentationTime; // for next time
40       fSamePresentationTimeCounter = 0; // for next time
41     }
42     fOutFid = OpenOutputFile(envir(), fPerFrameFileNameBuffer);
43   }
44 
45   // Write to our file:
46 #ifdef TEST_LOSS
47   static unsigned const framesPerPacket = 10;
48   static unsigned const frameCount = 0;
49   static Boolean const packetIsLost;
50   if ((frameCount++)%framesPerPacket == 0) {
51     packetIsLost = (our_random()%10 == 0); // simulate 10% packet loss #####
52   }
53 
54   if (!packetIsLost)
55 #endif
56   if (fOutFid != NULL && data != NULL) {
57     fwrite(data, 1, dataSize, fOutFid);          // 写数据到文件
58   }
59 }
60 
61 
62 void FileSink::afterGettingFrame(unsigned frameSize,
63                  unsigned numTruncatedBytes,
64                  struct timeval presentationTime) {
65   if (numTruncatedBytes > 0) {
66     envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size ("
67         << fBufferSize << ").  "
68             << numTruncatedBytes << " bytes of trailing data was dropped!  Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call to at least "
69             << fBufferSize + numTruncatedBytes << "\n";
70   }
71   addData(fBuffer, frameSize, presentationTime);        // 写视频数据到文件
72 
73   if (fOutFid == NULL || fflush(fOutFid) == EOF) {
74     // The output file has closed.  Handle this the same way as if the input source had closed:
75     if (fSource != NULL) fSource->stopGettingFrames();
76     onSourceClosure();
77     return;
78   }
79 
80   if (fPerFrameFileNameBuffer != NULL) {
81     if (fOutFid != NULL) { fclose(fOutFid); fOutFid = NULL; }
82   }
83 
84   // Then try getting the next frame:
85   continuePlaying();                // 获取下一帧的数据
86 }

  H264VideoFileSink从FramedSource获取一帧数据后,先写H264帧的头部,然后再写视频数据,然后再获取下一帧数据,如此不断地将获取的视频数据写到录像文件中去。

 

posted @ 2015-05-03 17:42  jqc  阅读(4240)  评论(0编辑  收藏  举报