258 lines
7.0 KiB
C++
258 lines
7.0 KiB
C++
#include "rtmp-internal.h"
|
|
#include "rtmp-msgtypeid.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
static struct rtmp_packet_t* rtmp_packet_find(struct rtmp_t* rtmp, uint32_t cid)
|
|
{
|
|
uint32_t i;
|
|
struct rtmp_packet_t* pkt;
|
|
|
|
// The protocol supports up to 65597 streams with IDs 3-65599
|
|
assert(cid <= 65535 + 64 && cid >= 2 /* Protocol Control Messages */);
|
|
for (i = 0; i < N_CHUNK_STREAM; i++)
|
|
{
|
|
pkt = rtmp->in_packets + ((i + cid) % N_CHUNK_STREAM);
|
|
if (pkt->header.cid == cid)
|
|
return pkt;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct rtmp_packet_t* rtmp_packet_create(struct rtmp_t* rtmp, uint32_t cid)
|
|
{
|
|
uint32_t i;
|
|
struct rtmp_packet_t* pkt;
|
|
|
|
// The protocol supports up to 65597 streams with IDs 3-65599
|
|
assert(cid <= 65535 + 64 && cid >= 2 /* Protocol Control Messages */);
|
|
assert(NULL == rtmp_packet_find(rtmp, cid));
|
|
for (i = 0; i < N_CHUNK_STREAM; i++)
|
|
{
|
|
pkt = rtmp->in_packets + ((i + cid) % N_CHUNK_STREAM);
|
|
if (0 == pkt->header.cid)
|
|
return pkt;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct rtmp_packet_t* rtmp_packet_parse(struct rtmp_t* rtmp, const uint8_t* buffer)
|
|
{
|
|
uint8_t fmt = 0;
|
|
uint32_t cid = 0;
|
|
struct rtmp_packet_t* packet;
|
|
|
|
// chunk base header
|
|
buffer += rtmp_chunk_basic_header_read(buffer, &fmt, &cid);
|
|
|
|
// load previous header
|
|
packet = rtmp_packet_find(rtmp, cid);
|
|
if (NULL == packet)
|
|
{
|
|
if (RTMP_CHUNK_TYPE_0 != fmt && RTMP_CHUNK_TYPE_1 != fmt)
|
|
return NULL; // don't know stream length
|
|
|
|
packet = rtmp_packet_create(rtmp, cid);
|
|
if (NULL == packet)
|
|
return NULL;
|
|
}
|
|
|
|
// chunk message header
|
|
packet->header.cid = cid;
|
|
packet->header.fmt = fmt;
|
|
rtmp_chunk_message_header_read(buffer, &packet->header);
|
|
|
|
return packet;
|
|
}
|
|
|
|
static int rtmp_packet_alloc(struct rtmp_t* rtmp, struct rtmp_packet_t* packet)
|
|
{
|
|
void* p;
|
|
(void)rtmp;
|
|
|
|
// 24-bytes length
|
|
assert(0 == packet->bytes);
|
|
assert(packet->header.length < (1 << 24));
|
|
// fixed SMS (Chinacache Smart Media Server) packet->header.length = 0
|
|
if (0 == packet->capacity || packet->capacity < packet->header.length)
|
|
{
|
|
p = realloc(packet->payload, packet->header.length + 1024);
|
|
if (NULL == p)
|
|
return -ENOMEM;
|
|
packet->payload = p;
|
|
packet->capacity = packet->header.length + 1024;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rtmp_chunk_read(struct rtmp_t* rtmp, const uint8_t* data, size_t bytes)
|
|
{
|
|
const static uint32_t s_header_size[] = { 11, 7, 3, 0 };
|
|
|
|
int r, invalid_extended_timestamp;
|
|
size_t size, offset = 0;
|
|
uint8_t extended_timestamp_buffer[4];
|
|
uint32_t extended_timestamp = 0;
|
|
struct rtmp_parser_t* parser = &rtmp->parser;
|
|
struct rtmp_chunk_header_t header;
|
|
|
|
while (offset < bytes)
|
|
{
|
|
switch (parser->state)
|
|
{
|
|
case RTMP_PARSE_INIT:
|
|
parser->pkt = NULL;
|
|
parser->bytes = 1;
|
|
parser->buffer[0] = data[offset++];
|
|
|
|
if (0 == (parser->buffer[0] & 0x3F))
|
|
parser->basic_bytes = 2;
|
|
else if (1 == (parser->buffer[0] & 0x3F))
|
|
parser->basic_bytes = 3;
|
|
else
|
|
parser->basic_bytes = 1;
|
|
|
|
parser->state = RTMP_PARSE_BASIC_HEADER;
|
|
break;
|
|
|
|
case RTMP_PARSE_BASIC_HEADER:
|
|
assert(parser->bytes <= parser->basic_bytes);
|
|
while (parser->bytes < parser->basic_bytes && offset < bytes)
|
|
{
|
|
parser->buffer[parser->bytes++] = data[offset++];
|
|
}
|
|
|
|
assert(parser->bytes <= parser->basic_bytes);
|
|
if (parser->bytes >= parser->basic_bytes)
|
|
{
|
|
parser->state = RTMP_PARSE_MESSAGE_HEADER;
|
|
}
|
|
break;
|
|
|
|
case RTMP_PARSE_MESSAGE_HEADER:
|
|
size = s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes;
|
|
assert(parser->bytes <= size);
|
|
while (parser->bytes < size && offset < bytes)
|
|
{
|
|
parser->buffer[parser->bytes++] = data[offset++];
|
|
}
|
|
|
|
assert(parser->bytes <= size);
|
|
if (parser->bytes >= size)
|
|
{
|
|
parser->pkt = rtmp_packet_parse(rtmp, parser->buffer);
|
|
parser->state = RTMP_PARSE_EXTENDED_TIMESTAMP;
|
|
}
|
|
break;
|
|
|
|
case RTMP_PARSE_EXTENDED_TIMESTAMP:
|
|
if (NULL == parser->pkt) return -ENOMEM;
|
|
|
|
assert(parser->pkt->header.timestamp <= 0xFFFFFF);
|
|
size = s_header_size[parser->pkt->header.fmt] + parser->basic_bytes;
|
|
if (parser->pkt->header.timestamp == 0xFFFFFF) size += 4; // extended timestamp
|
|
|
|
assert(parser->bytes <= size);
|
|
while (parser->bytes < size && offset < bytes)
|
|
{
|
|
parser->buffer[parser->bytes++] = data[offset++];
|
|
}
|
|
|
|
assert(parser->bytes <= size);
|
|
if (parser->bytes >= size)
|
|
{
|
|
invalid_extended_timestamp = 0;
|
|
extended_timestamp = parser->pkt->header.timestamp;
|
|
if (parser->pkt->header.timestamp == 0xFFFFFF)
|
|
{
|
|
// parse extended timestamp
|
|
rtmp_chunk_extended_timestamp_read(parser->buffer + s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes, &extended_timestamp);
|
|
if (RTMP_CHUNK_TYPE_3 == parser->pkt->header.fmt && extended_timestamp != parser->pkt->delta)
|
|
{
|
|
extended_timestamp = parser->pkt->delta; // use previous delta
|
|
|
|
// fix code offset -= 4 on offset < 4;
|
|
invalid_extended_timestamp = 1;
|
|
memcpy(extended_timestamp_buffer, parser->buffer + s_header_size[parser->buffer[0] >> 6] + parser->basic_bytes, 4);
|
|
}
|
|
}
|
|
|
|
// first chunk
|
|
if (0 == parser->pkt->bytes)
|
|
{
|
|
parser->pkt->delta = extended_timestamp;
|
|
|
|
// handle timestamp/delta
|
|
if (RTMP_CHUNK_TYPE_0 == parser->pkt->header.fmt)
|
|
parser->pkt->clock = parser->pkt->delta;
|
|
else
|
|
parser->pkt->clock += parser->pkt->delta;
|
|
|
|
if (0 != rtmp_packet_alloc(rtmp, parser->pkt))
|
|
return -ENOMEM;
|
|
}
|
|
parser->state = RTMP_PARSE_PAYLOAD;
|
|
|
|
// rewind extended_timestamp_buffer
|
|
if (invalid_extended_timestamp)
|
|
{
|
|
r = rtmp_chunk_read(rtmp, extended_timestamp_buffer, 4);
|
|
if (0 != r) return r;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RTMP_PARSE_PAYLOAD:
|
|
if (NULL == parser->pkt || NULL == parser->pkt->payload
|
|
|| parser->pkt->bytes > parser->pkt->capacity
|
|
|| parser->pkt->bytes > parser->pkt->header.length
|
|
|| parser->pkt->header.length > parser->pkt->capacity)
|
|
{
|
|
assert(0);
|
|
return -ENOMEM;
|
|
}
|
|
//assert(parser->pkt->bytes <= parser->pkt->header.length);
|
|
//assert(parser->pkt->capacity >= parser->pkt->header.length);
|
|
size = MIN(rtmp->in_chunk_size - (parser->pkt->bytes % rtmp->in_chunk_size), parser->pkt->header.length - parser->pkt->bytes);
|
|
size = MIN(size, bytes - offset);
|
|
if(size > 0) memcpy(parser->pkt->payload + parser->pkt->bytes, data + offset, size);
|
|
parser->pkt->bytes += size;
|
|
offset += size;
|
|
|
|
if (parser->pkt->bytes >= parser->pkt->header.length)
|
|
{
|
|
assert(parser->pkt->bytes == parser->pkt->header.length);
|
|
parser->state = RTMP_PARSE_INIT; // reset parser state
|
|
parser->pkt->bytes = 0; // clear bytes
|
|
|
|
memcpy(&header, &parser->pkt->header, sizeof(header));
|
|
header.timestamp = parser->pkt->clock;
|
|
r = rtmp_handler(rtmp, &header, parser->pkt->payload);
|
|
if(0 != r) return r;
|
|
}
|
|
else if (0 == (parser->pkt->bytes % rtmp->in_chunk_size))
|
|
{
|
|
// next chunk
|
|
parser->state = RTMP_PARSE_INIT;
|
|
}
|
|
else
|
|
{
|
|
// need more data
|
|
assert(offset == bytes);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|