RTMPdump 源代码分析 1: main()函数
rtmpdump 是一个用来处理 RTMP 流媒体的工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps:// 等。之前在学习RTMP协议的时候,发现没有讲它源代码的,只好自己分析,现在打算把自己学习的成果写出来,可能结果不一定都对,先暂且记录一下。
使用RTMPdump下载一个流媒体的大致流程是这样的:
intDownload(RTMP * rtmp,// connected RTMP object FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, int bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent)// percentage downloaded [out]{ int32_t now, lastUpdate; int bufferSize = 64 * 1024; char *buffer = (char *) malloc(bufferSize); int nRead = 0; //long ftell(FILE *stream); //返回当前文件指针 RTMP_LogPrintf("开始下载!\n"); off_t size = ftello(file); unsigned long lastPercent = 0; //时间戳 rtmp->m_read.timestamp = dSeek; *percent = 0.0; if (rtmp->m_read.timestamp) { RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp); } //是直播 if (bLiveStream) { RTMP_LogPrintf("直播流\n"); } else { // print initial status // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded if (duration > 0){ if ((double) rtmp->m_read.timestamp >= (double) duration * 999.0) { RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",(double) rtmp->m_read.timestamp / 1000.0,(double) duration / 1000.0); return RD_SUCCESS; } else { *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",bResume ? "Resuming" : "Starting",(double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,*percent); }} else{ RTMP_LogPrintf("%s download at: %.3f kB\n", bResume ? "Resuming" : "Starting", (double) size / 1024.0);} } if (dStopOffset > 0) RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0); //各种设置参数到rtmp连接 if (bResume && nInitialFrameSize > 0) rtmp->m_read.flags |= RTMP_READ_RESUME; rtmp->m_read.initialFrameType = initialFrameType; rtmp->m_read.nResumeTS = dSeek; rtmp->m_read.metaHeader = metaHeader; rtmp->m_read.initialFrame = initialFrame; rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize; rtmp->m_read.nInitialFrameSize = nInitialFrameSize; now = RTMP_GetTime(); lastUpdate = now - 1000; do {//从rtmp中把bufferSize(64k)个数据读入buffer nRead = RTMP_Read(rtmp, buffer, bufferSize); //RTMP_LogPrintf("nRead: %d\n", nRead); if (nRead > 0){//函数:size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);//向文件读入写入一个数据块。返回值:返回实际写入的数据块数目//(1)buffer:是一个指针,对fwrite来说,是要输出数据的地址。//(2)size:要写入内容的单字节数; //(3)count:要进行写入size字节的数据项的个数; //(4)stream:目标文件指针。 //(5)返回实际写入的数据项个数count。//关键。把buffer里面的数据写成文件if (fwrite(buffer, sizeof(unsigned char), nRead, file) != (size_t) nRead) { RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__); free(buffer); return RD_FAILED; }//记录已经写入的字节数 size += nRead; //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0); if (duration <= 0)// if duration unknown try to get it from the stream (onMetaData) duration = RTMP_GetDuration(rtmp); if (duration > 0) { // make sure we claim to have enough buffer time! if (!bOverrideBufferTime && bufferTime < (duration * 1000.0)){ bufferTime = (uint32_t) (duration * 1000.0) + 5000;// 再加5s以确保buffertime足够长 RTMP_Log(RTMP_LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", bufferTime); //重设Buffer长度 RTMP_SetBufferMS(rtmp, bufferTime); //给服务器发送UserControl消息通知Buffer改变 RTMP_UpdateBufferMS(rtmp);} //计算百分比 *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; if (bHashes){ if (lastPercent + 1 <= *percent) { RTMP_LogStatus("#"); lastPercent = (unsigned long) *percent; }} else{//设置显示数据的更新间隔200ms now = RTMP_GetTime(); if (abs(now - lastUpdate) > 200) { RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",(double) size / 1024.0,(double) (rtmp->m_read.timestamp) / 1000.0, *percent); lastUpdate = now; }} } else {//现在距离开机的毫秒数 now = RTMP_GetTime(); //每间隔200ms刷新一次数据 if (abs(now - lastUpdate) > 200){ if (bHashes) RTMP_LogStatus("#"); else//size为已写入文件的字节数 RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0); lastUpdate = now;} }}#ifdef _DEBUG else{ RTMP_Log(RTMP_LOGDEBUG, "zero read!");}#endif } while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp)); free(buffer); if (nRead < 0)//nRead是读取情况 nRead = rtmp->m_read.status; /* Final status update */ if (!bHashes) { if (duration > 0){ *percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0; *percent = ((double) (int) (*percent * 10.0)) / 10.0; //输出 RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0, *percent);} else{ RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0, (double) (rtmp->m_read.timestamp) / 1000.0);} } RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead); //读取错误 if (bResume && nRead == -2) { RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",nSkipKeyFrames + 1); return RD_FAILED; } //读取正确 if (nRead == -3) return RD_SUCCESS; //没读完... if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0 || RTMP_IsTimedout(rtmp)) { return RD_INCOMPLETE; } return RD_SUCCESS;}