现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

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

2019-08-27 14:29 工业·编程 ⁄ 共 21426字 ⁄ 字号 暂无评论

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帧的头部,然后再写视频数据,然后再获取下一帧数据,如此不断地将获取的视频数据写到录像文件中去。

作者:昨夜星辰

给我留言

留言无头像?