LibRTMP源代码分析10:处理消息(Handle Message)

转载自:http://nkwavelet.blog.163.com/blog/static/227756038201412473745809/

函数RTMP_ClientPacket()是libRTMP中最重要的核心函数之一,主要完成各种消息的处理。

/**
 * @brief 根据接收到的消息类型的不同,作出不同的处理。
 *  消息类型有协议控制消息、Flv数据、Flex消息等。
 *  协议控制消息的定义可参考官方协议:RTMP消息格式(第5章)
 *
 * @return 如果含有多媒体(音视频或metadata)数据,则返回1,否则返回0.
 */
int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)
{
int bHasMediaPacket = 0; // 是否含有音视频或metadata数据
switch (packet->m_packetType)
{
case RTMP_PACKET_TYPE_CHUNK_SIZE:
/* chunk size,设置块大小 */
HandleChangeChunkSize(r, packet);
break;

case RTMP_PACKET_TYPE_BYTES_READ_REPORT:
/* bytes read report,致谢 */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: bytes read report", __FUNCTION__);
break;

case RTMP_PACKET_TYPE_CONTROL:
/* ctrl,处理用户控制消息 */
HandleCtrl(r, packet);
break;

case RTMP_PACKET_TYPE_SERVER_BW:
/* server bw,致谢窗口大小 */
HandleServerBW(r, packet);
break;

case RTMP_PACKET_TYPE_CLIENT_BW:
/* client bw,设置对等端带宽 */
HandleClientBW(r, packet);
break;

case RTMP_PACKET_TYPE_AUDIO:
/* audio data,音频数据 */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); */
HandleAudio(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;

case RTMP_PACKET_TYPE_VIDEO:
/* video data,视频数据 */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); */
HandleVideo(r, packet);
bHasMediaPacket = 1;
if (!r->m_mediaChannel)
r->m_mediaChannel = packet->m_nChannel;
if (!r->m_pausing)
r->m_mediaStamp = packet->m_nTimeStamp;
break;

case RTMP_PACKET_TYPE_FLEX_STREAM_SEND:
/* flex stream send,AMF3编码,忽略 */
RTMP_Log(RTMP_LOGDEBUG, "%s, flex stream send, size %u bytes, not supported, ignoring", 
__FUNCTION__, packet->m_nBodySize);
break;

case RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT:
/* flex shared object,AMF3编码,忽略 */
RTMP_Log(RTMP_LOGDEBUG, "%s, flex shared object, size %u bytes, not supported, ignoring", 
__FUNCTION__, packet->m_nBodySize);
break;

case RTMP_PACKET_TYPE_FLEX_MESSAGE:
/* flex message,AMF3编码 */
{
RTMP_Log(RTMP_LOGDEBUG, "%s, flex message, size %u bytes, not fully supported", 
 __FUNCTION__, packet->m_nBodySize);
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */

/* some DEBUG code */
#if 0
RTMP_LIB_AMFObject obj;
int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1);
if(nRes < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__);
/*return; */
}
obj.Dump();
#endif
if (HandleInvoke(r, packet->m_body + 1, packet->m_nBodySize - 1) == 1)
bHasMediaPacket = 2;
break;
}

case RTMP_PACKET_TYPE_INFO:
/* metadata (notify),元数据,AMF0编码 */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: notify %u bytes", __FUNCTION__, packet->m_nBodySize);
if (HandleMetadata(r, packet->m_body, packet->m_nBodySize))
bHasMediaPacket = 1;
break;

case RTMP_PACKET_TYPE_SHARED_OBJECT:
/* AMF0编码,忽略 */
RTMP_Log(RTMP_LOGDEBUG, "%s, shared object, not supported, ignoring", __FUNCTION__);
break;

case RTMP_PACKET_TYPE_INVOKE:
/* invoke,AMF0编码的命令消息,例如各种控制命令:播放、暂停、停止 */
RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %u bytes", 
 __FUNCTION__, packet->m_nBodySize);
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */

if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1)
bHasMediaPacket = 2;
break;

case RTMP_PACKET_TYPE_FLASH_VIDEO:
{
/* go through FLV packets and handle metadata packets */
unsigned int pos = 0;
uint32_t nTimeStamp = packet->m_nTimeStamp;

while (pos + 11 < packet->m_nBodySize)
{
// Flv Tag = Type(1字节) + Datasize(3字节) + Timestamp(3字节) + TimestampExtended(1字节) 
//          + 0x000000(3字节) + Data(Datasize字节) + PreviousTagSize(4字节)
// Flv Tag = TagHeader(11字节) + Data(Datasize字节) + PreviousTagSize(4字节)
/* size without header (11) and prevTagSize (4) */
uint32_t dataSize = AMF_DecodeInt24(packet->m_body + pos + 1);

if (pos + 11 + dataSize + 4 > packet->m_nBodySize)
{
RTMP_Log(RTMP_LOGWARNING, "Stream corrupt?!");
break;
}

if (packet->m_body[pos] == 0x12) // Metadata Tag
{
HandleMetadata(r, packet->m_body + pos + 11, dataSize);
}
else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9) // 音频或视频Tag
{
nTimeStamp  = AMF_DecodeInt24(packet->m_body + pos + 4); // 时间戳
nTimeStamp |= (packet->m_body[pos + 7] << 24); // 扩展的时间戳
}

pos += (11 + dataSize + 4);
}

if (!r->m_pausing)
r->m_mediaStamp = nTimeStamp;

/* FLV tag(s) */
/*RTMP_Log(RTMP_LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", 
   __FUNCTION__, packet.m_nBodySize); */
bHasMediaPacket = 1;
break;
}

default:
RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", 
 __FUNCTION__, packet->m_packetType);
#ifdef _DEBUG
RTMP_LogHex(RTMP_LOGDEBUG, packet->m_body, packet->m_nBodySize);
#endif
}
return bHasMediaPacket;
}

该函数的大体思路是,根据接收到的消息(Message)类型的不同,做出不同的响应。例如收到的消息类型为0x01,那么就是设置块(Chunk)大小的协议,那么就调用相应的函数进行处理。因此,本函数可以说是程序的灵魂,收到的各种命令消息都要经过本函数的判断决定调用哪个函数进行相应的处理。  下面我们按照消息ID从小到大的顺序,看看接收到的各种消息都是如何处理的。

/**
 * @brief 改变块大小(消息类型ID为0x01
 */
static void HandleChangeChunkSize(RTMP *r, const RTMPPacket *packet)
{
if (packet->m_nBodySize >= 4)
{
r->m_inChunkSize = AMF_DecodeInt32(packet->m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s, received: chunk size change to %d", __FUNCTION__, r->m_inChunkSize);
}
}

/**
 * @brief 处理用户控制(UserControl)消息(消息类型ID为0x04)
*      用户控制消息是服务器端发出的。 可参考rtmp协议:rtmp命令消息 -- 3.7节
 */
static void HandleCtrl(RTMP *r, const RTMPPacket *packet)
{
short nType = -1;
unsigned int tmp;
if (packet->m_body && packet->m_nBodySize >= 2)
nType = AMF_DecodeInt16(packet->m_body); // 事件类型(2B)

RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType, packet->m_nBodySize);
/*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */

if (packet->m_nBodySize >= 6)
{
switch (nType)
{
case 0: // 流开始
tmp = AMF_DecodeInt32(packet->m_body + 2); // 流ID
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream Begin %d", __FUNCTION__, tmp);
break;

case 1: // 流结束
tmp = AMF_DecodeInt32(packet->m_body + 2); // 流ID
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream EOF %d", __FUNCTION__, tmp);
if (r->m_pausing == 1)
r->m_pausing = 2;
break;

case 2: // 流枯竭
tmp = AMF_DecodeInt32(packet->m_body + 2); // 流ID
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream Dry %d", __FUNCTION__, tmp);
break;

case 4: // 是录制流
tmp = AMF_DecodeInt32(packet->m_body + 2);
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream IsRecorded %d", __FUNCTION__, tmp);
break;

case 6: // Ping客户端 /* server ping. reply with pong. */
tmp = AMF_DecodeInt32(packet->m_body + 2);
RTMP_Log(RTMP_LOGDEBUG, "%s, Ping %d", __FUNCTION__, tmp);
RTMP_SendCtrl(r, 0x07, tmp, 0);
break;

/* FMS 3.5 servers send the following two controls to let the client
 * know when the server has sent a complete buffer. I.e., when the
 * server has sent an amount of data equal to m_nBufferMS in duration.
 * The server meters its output so that data arrives at the client
 * in realtime and no faster.
 *
 * The rtmpdump program tries to set m_nBufferMS as large as
 * possible, to force the server to send data as fast as possible.
 * In practice, the server appears to cap this at about 1 hour's
 * worth of data. After the server has sent a complete buffer, and
 * sends this BufferEmpty message, it will wait until the play
 * duration of that buffer has passed before sending a new buffer.
 * The BufferReady message will be sent when the new buffer starts.
 * (There is no BufferReady message for the very first buffer;
 * presumably the Stream Begin message is sufficient for that
 * purpose.)
 *
 * If the network speed is much faster than the data bitrate, then
 * there may be long delays between the end of one buffer and the
 * start of the next.
 *
 * Since usually the network allows data to be sent at
 * faster than realtime, and rtmpdump wants to download the data
 * as fast as possible, we use this RTMP_LF_BUFX hack: when we
 * get the BufferEmpty message, we send a Pause followed by an
 * Unpause. This causes the server to send the next buffer immediately
 * instead of waiting for the full duration to elapse. (That's
 * also the purpose of the ToggleStream function, which rtmpdump
 * calls if we get a read timeout.)
 *
 * Media player apps don't need this hack since they are just
 * going to play the data in realtime anyway. It also doesn't work
 * for live streams since they obviously can only be sent in
 * realtime. And it's all moot if the network speed is actually
 * slower than the media bitrate.
 */
case 31:
tmp = AMF_DecodeInt32(packet->m_body + 2);
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream BufferEmpty %d", __FUNCTION__, tmp);
if (!(r->Link.lFlags & RTMP_LF_BUFX))
break;

if (!r->m_pausing)
{
r->m_pauseStamp = r->m_channelTimestamp[r->m_mediaChannel];
RTMP_SendPause(r, TRUE, r->m_pauseStamp);
r->m_pausing = 1;
}
else if (r->m_pausing == 2)
{
RTMP_SendPause(r, FALSE, r->m_pauseStamp);
r->m_pausing = 3;
}
break;

case 32:
tmp = AMF_DecodeInt32(packet->m_body + 2);
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream BufferReady %d", __FUNCTION__, tmp);
break;

default:
tmp = AMF_DecodeInt32(packet->m_body + 2);
RTMP_Log(RTMP_LOGDEBUG, "%s, Stream xx %d", __FUNCTION__, tmp);
break;
}

}

if (nType == 0x1A)
{
RTMP_Log(RTMP_LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__);
if (packet->m_nBodySize > 2 && packet->m_body[2] > 0x01)
{
RTMP_Log(RTMP_LOGERROR, "%s: SWFVerification Type %d request not supported! 
 Patches welcome...", __FUNCTION__, packet->m_body[2]);
}

RTMP_Log(RTMP_LOGERROR, "%s: Ignoring SWFVerification request, no CRYPTO support!", __FUNCTION__);
}
}

/**
 * @brief 致谢消息的窗口大小(服务端)(消息类型ID为0x05
 */
static void HandleServerBW(RTMP *r, const RTMPPacket *packet)
{
r->m_nServerBW = AMF_DecodeInt32(packet->m_body);
RTMP_Log(RTMP_LOGDEBUG, "%s: server BW = %d", __FUNCTION__, r->m_nServerBW);
}

/**
 * @brief 对等端带宽(客户端)
 */
static void HandleClientBW(RTMP *r, const RTMPPacket *packet)
{
r->m_nClientBW = AMF_DecodeInt32(packet->m_body);
if (packet->m_nBodySize > 4)
r->m_nClientBW2 = packet->m_body[4]; // 限制类型:硬(0),软(1),或者动态(2)
else
r->m_nClientBW2 = -1;

RTMP_Log(RTMP_LOGDEBUG, "%s: client BW = %d %d", __FUNCTION__, r->m_nClientBW, r->m_nClientBW2);
}

消息类型 ID 是 0x03 的消息功能是“致谢”,没有处理函数。
消息类型 ID 是 0x08 的消息用于传输音频数据,在这里不处理。
消息类型 ID 是 0x09 的消息用于传输音频数据,在这里不处理。
消息类型 ID 是 0x0F、0x10 和 0x11 的消息用于传输AMF3编码的命令。
消息类型 ID 是 0x12、0x13 和 0x14 的消息用于传输AMF0编码的命令。
注意:消息类型 ID 是 0x14 的消息很重要,用于传输AMF0编码的命令, 例如各种控制命令:播放、暂停、停止等。

/**
 * @brief 处理服务器发来的AMF0编码的命令.主要的步骤有:
 *
 *  1) 调用AMF_Decode()解码AMF命令数据
 *  2) 调用AMFProp_GetString()获取具体命令的字符串
 *  3) 调用AVMATCH()比较字符串,不同的命令做不同的处理
 */
static int HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
{
AMFObject obj;
AVal method;
double txn;
int ret = 0, nRes;
// 起始字节为包类型AMF_STRING(0x02), 表示接下来是命令字符串
if (body[0] != 0x02) /* make sure it is a string method name we start with */
{
RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet", __FUNCTION__);
return 0;
}

// 解码一个AMF对象
nRes =  AMF_Decode(&obj, body, nBodySize, FALSE);
if (nRes < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);
return 0;
}

AMF_Dump(&obj);
AMFProp_GetString( AMF_GetProp(&obj, NULL, 0), &method); // 获取AMF对象obj第0个属性的AVal值
txn =  AMFProp_GetNumber( AMF_GetProp(&obj, NULL, 1)); // 获取AMF对象obj第1个属性的double值
RTMP_Log(RTMP_LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val);

if (AVMATCH(&method, &av__result))
{
AVal methodInvoked = {0};
int i;

for (i=0; i<r->m_numCalls; i++) 
{
if (r->m_methodCalls[i].num == (int)txn)
{
methodInvoked = r->m_methodCalls[i].name;
AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE);
break;
}
}

if (!methodInvoked.av_val) 
{
RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %f without matching request", __FUNCTION__, txn);
goto leave;
}

RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", 
 __FUNCTION__, methodInvoked.av_val);

if (AVMATCH(&methodInvoked, &av_connect))
{
if (r->Link.token.av_len)
{
AMFObjectProperty p;
if ( RTMP_FindFirstMatchingProperty(&obj, &av_secureToken, &p))
{
DecodeTEA(&r->Link.token, &p.p_vu.p_aval);
SendSecureTokenResponse(r, &p.p_vu.p_aval);
}
}
if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
SendReleaseStream(r);
SendFCPublish(r);
}
else
{
RTMP_SendServerBW(r);
RTMP_SendCtrl(r, 3, 0, 300);
}

RTMP_SendCreateStream(r);

if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
{
/* Authenticate on Justin.tv legacy servers before sending FCSubscribe */
if (r->Link.usherToken.av_len)
SendUsherToken(r, &r->Link.usherToken);
/* Send the FCSubscribe if live stream or if subscribepath is set */
if (r->Link.subscribepath.av_len)
SendFCSubscribe(r, &r->Link.subscribepath);
else if (r->Link.lFlags & RTMP_LF_LIVE)
SendFCSubscribe(r, &r->Link.playpath);
}
}
// 建立网络流,通常发生在建立网络连接(NetConnection)之后,播放(Play)之前
else if (AVMATCH(&methodInvoked, &av_createStream))
{
r->m_stream_id = (int) AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));

if (r->Link.protocol & RTMP_FEATURE_WRITE)
{
SendPublish(r);
}
else
{
if (r->Link.lFlags & RTMP_LF_PLST)
SendPlaylist(r); // 获取播放列表
SendPlay(r); // 发送命令消息“play”,开始播放流媒体数据
RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS);
}
}
else if (AVMATCH(&methodInvoked, &av_play) || AVMATCH(&methodInvoked, &av_publish))
{
r->m_bPlaying = TRUE;
}

free(methodInvoked.av_val);
}
else if (AVMATCH(&method, &av_onBWDone))
{
if (!r->m_nBWCheckCounter)
SendCheckBW(r);
}
else if (AVMATCH(&method, &av_onFCSubscribe))
{
/* SendOnFCSubscribe(); */
}
else if (AVMATCH(&method, &av_onFCUnsubscribe))
{
RTMP_Close(r);
ret = 1;
}
else if (AVMATCH(&method, &av_ping))
{
SendPong(r, txn);
}
else if (AVMATCH(&method, &av__onbwcheck))
{
SendCheckBWResult(r, txn);
}
else if (AVMATCH(&method, &av__onbwdone))
{
int i;
for (i = 0; i < r->m_numCalls; i++)
if (AVMATCH(&r->m_methodCalls[i].name, &av__checkbw))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
else if (AVMATCH(&method, &av__error))
{
RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
}
else if (AVMATCH(&method, &av_close))
{
RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
RTMP_Close(r);
}
else if (AVMATCH(&method, &av_onStatus))
{
AMFObject obj2;
AVal code, level;
AMFProp_GetObject( AMF_GetProp(&obj, NULL, 3), &obj2);
AMFProp_GetString( AMF_GetProp(&obj2, &av_code, -1), &code);
AMFProp_GetString( AMF_GetProp(&obj2, &av_level, -1), &level);

RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
if (AVMATCH(&code, &av_NetStream_Failed)
|| AVMATCH(&code, &av_NetStream_Play_Failed)
|| AVMATCH(&code, &av_NetStream_Play_StreamNotFound)
|| AVMATCH(&code, &av_NetConnection_Connect_InvalidApp))
{
r->m_stream_id = -1;
RTMP_Close(r);
RTMP_Log(RTMP_LOGERROR, "Closing connection: %s", code.av_val);
}
else if (AVMATCH(&code, &av_NetStream_Play_Start) || AVMATCH(&code, &av_NetStream_Play_PublishNotify))
{
int i;
r->m_bPlaying = TRUE;
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_play))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
else if (AVMATCH(&code, &av_NetStream_Publish_Start))
{
int i;
r->m_bPlaying = TRUE;
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_publish))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
/* Return 1 if this is a Play.Complete or Play.Stop */
else if (AVMATCH(&code, &av_NetStream_Play_Complete)
|| AVMATCH(&code, &av_NetStream_Play_Stop)
|| AVMATCH(&code, &av_NetStream_Play_UnpublishNotify))
{
RTMP_Close(r);
ret = 1;
}
else if (AVMATCH(&code, &av_NetStream_Seek_Notify))
{
r->m_read.flags &= ~RTMP_READ_SEEKING;
}
else if (AVMATCH(&code, &av_NetStream_Pause_Notify))
{
if (r->m_pausing == 1 || r->m_pausing == 2)
{
RTMP_SendPause(r, FALSE, r->m_pauseStamp);
r->m_pausing = 3;
}
}
    }
else if (AVMATCH(&method, &av_playlist_ready))
{
int i;
for (i = 0; i < r->m_numCalls; i++)
{
if (AVMATCH(&r->m_methodCalls[i].name, &av_set_playlist))
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE);
break;
}
}
}
else
{
}

leave:
AMF_Reset(&obj);
return ret;
}

上面红色标记的函数大部分已经在前面的章节中介绍过了,下面简单介绍一下剩下的几个函数:

/**
 * @brief 查找AMF对象obj中第一个与指定name匹配的属性 
 * @return 查找成功返回TRUE,否则返回FALSE
 */
int RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, AMFObjectProperty * p);

/**
 * @brief 检测指定name是否是AMF对象obj某个属性名字的前缀.
 *   Like above, but only check if name is a prefix of property.
 * @return 查找成功返回TRUE,否则返回FALSE
 */
int RTMP_FindPrefixProperty(AMFObject *obj, const AVal *name, AMFObjectProperty * p);

/**
 * @brief 以小端字节序解码一个32位无符号整数.
 */
static int DecodeInt32LE(const char *data);

/**
 * @brief 以小端字节序编码一个32位整数.
 */
static int EncodeInt32LE(char *output, int nVal);

/**
 * @brief 解密用TEA加密过的密钥
 *
 * @param key  : 用TEA加密过的密钥
 * @param text : 用于保存解密出来的结果
 */
static void DecodeTEA(AVal *key, AVal *text);

/**
 * @brief 将第i个方法从队列vals中删除掉
 */
static void AV_erase(RTMP_METHOD *vals, int *num, int i, int freeit)
{
if (freeit)
free(vals[i].name.av_val);
// 队列中的方法数量减一
(*num)--;

// 第i个方法之后的方法往前移动一个位置
for (; i < *num; i++)
vals[i] = vals[i + 1];
// 最后一个方法(即第*num个方法)置空
vals[i].name.av_val = NULL;
vals[i].name.av_len = 0;
vals[i].num = 0;
}

/**
 * @brief 将RTMP待调用的方法队列中的第i个方法删除掉
 */
void RTMP_DropRequest(RTMP *r, int i, int freeit)
{
AV_erase(r->m_methodCalls, &r->m_numCalls, i, freeit);
}

/**
 * @brief 将新方法av添加到方法队列vals中
 */
static void AV_queue(RTMP_METHOD **vals, int *num, AVal *av, int txn)
{
char *tmp;
if (!(*num & 0x0f))
*vals = realloc(*vals, (*num + 16) * sizeof(RTMP_METHOD));

tmp = malloc(av->av_len + 1);
memcpy(tmp, av->av_val, av->av_len);
tmp[av->av_len] = '\0';
(*vals)[*num].num = txn;
(*vals)[*num].name.av_len = av->av_len;
(*vals)[(*num)++].name.av_val = tmp;
}

/**
 * @brief 将方法队列vals中的所有方法清空
 */
static void AV_clear(RTMP_METHOD *vals, int num)
{
int i;
for (i = 0; i < num; i++)
free(vals[i].name.av_val);
free(vals);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值