cuticle/hi/rtmp.c

302 lines
6.8 KiB
C
Raw Normal View History

2025-03-09 10:29:35 +02:00
#include"node.h"
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<librtmp/rtmp.h>
#include<librtmp/log.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include"img.h"
#define NALLENSZ 4
static size_t annexb_parse(const uint8_t *src, const uint8_t *srcEnd) {
int zeros = 0;
const uint8_t *src2;
for(src2 = src; src2 != srcEnd; src2++) {
if(*src2 == 0) {
zeros++;
} else if((zeros == 2 || zeros == 3) && *src2 == 1) {
src2 -= zeros;
break;
} else {
zeros = 0;
}
}
return src2 - src;
}
static uint8_t *annexb_to_extradata(const uint8_t *src, const uint8_t *srcEnd, size_t *szRet, size_t *srcSzEnd) {
const uint8_t *sps = src;
while(*sps == 0) sps++;
assert(sps[0] == 1);
sps++;
size_t szSps = annexb_parse(sps, srcEnd);
const uint8_t *pps = sps + szSps;
while(*pps == 0) pps++;
assert(pps[0] == 1);
pps++;
size_t szPps = annexb_parse(pps, srcEnd);
uint8_t *ret = malloc(*szRet = (6 + 2 + szSps + 1 + 2 + szPps));
ret[0] = 1;
ret[1] = sps[1];
ret[2] = sps[2];
ret[3] = sps[3];
ret[4] = 0xFC | (NALLENSZ - 1);
ret[5] = 0xE0 | 1;
ret[6] = szSps >> 8;
ret[7] = szSps & 0xFF;
memcpy(&ret[8], sps, szSps);
ret[8 + szSps + 0] = 1;
ret[8 + szSps + 1] = szPps >> 8;
ret[8 + szSps + 2] = szPps & 0xFF;
memcpy(&ret[8 + szSps + 3], pps, szPps);
*srcSzEnd = pps + szPps - src;
return ret;
}
static uint8_t *annexb_to_avcc(const uint8_t *src, size_t szSrc, size_t *szRet) {
size_t cap = 4096, sz = 0;
uint8_t *ret = malloc(cap);
const uint8_t *srcEnd = src + szSrc;
while(src != srcEnd) {
assert(*src == 0);
while(*src == 0) {
src++;
}
assert(*src == 1);
src++;
size_t nalSize = annexb_parse(src, srcEnd);
size_t additionSz = NALLENSZ + nalSize;
if(sz + additionSz > cap) {
ret = realloc(ret, cap = (sz + additionSz));
}
*(uint32_t*) &ret[sz] = htonl(nalSize);
memcpy(&ret[sz + NALLENSZ], src, nalSize);
sz += additionSz;
src += nalSize;
}
*szRet = sz;
return ret;
}
typedef struct {
CHiPubNode pub;
RTMP *rtmp;
RTMPPacket rtmppkt;
} Internal;
static int streamrtmp_start(CHiPubNode *pub) {
Internal *n = (Internal*) pub;
n->rtmp = RTMP_Alloc();
if(!n->rtmp) return 0;
RTMP_Init(n->rtmp);
RTMP_LogSetLevel(RTMP_LOGINFO);
RTMP_LogSetOutput(stderr);
RTMP_SetupURL(n->rtmp, CHi_Crawl(&pub->sinks[2])->data.text);
RTMP_EnableWrite(n->rtmp);
if(!RTMP_Connect(n->rtmp, NULL)) {
return 0;
}
if(!RTMP_ConnectStream(n->rtmp, 0)) {
return 0;
}
memset(&n->rtmppkt, 0, sizeof(n->rtmppkt));
RTMPPacket_Alloc(&n->rtmppkt, 4096);
return 1;
}
static int streamrtmp_stop(CHiPubNode *pub) {
Internal *n = (Internal*) pub;
RTMP_Free(n->rtmp);
n->rtmp = NULL;
return 1;
}
#define FLV_TAG_HEADER_SIZE 11
#define FLV_VIDEO_HDR_SIZE 5
#define FLV_AUDIO_HDR_SIZE 2
#define FLV_PREV_TAG_SIZE_SIZE 4
static int do_video(Internal *n) {
if(!CHi_Crawl(&n->pub.sinks[0]) || !CHi_Crawl(&n->pub.sinks[0])->data.bitstream) {
return 1;
}
CHiBSFrames *frames = CHi_Crawl(&n->pub.sinks[0])->data.bitstream;
for(size_t fi = 0; fi < frames->count; fi++) {
CHiBSFrame *f = &frames->data[fi];
size_t avccSize;
uint8_t *avcc;
if(f->flags & CUTIHI_BS_SETUP_PACKET) {
size_t annexbextradatasrcsize;
avcc = annexb_to_extradata(f->ptr, f->ptr + f->sz, &avccSize, &annexbextradatasrcsize);
} else {
avcc = annexb_to_avcc(f->ptr, f->sz, &avccSize);
}
size_t dataSize = FLV_VIDEO_HDR_SIZE + avccSize;
size_t tagSize = FLV_TAG_HEADER_SIZE + dataSize;
size_t rtmpPacketSize = tagSize + FLV_PREV_TAG_SIZE_SIZE;
uint8_t *packet = malloc(rtmpPacketSize);
// Tag
packet[0] = 9; // video
packet[1] = (dataSize >> 16) & 0xFF;
packet[2] = (dataSize >> 8) & 0xFF;
packet[3] = (dataSize >> 0) & 0xFF;
packet[4] = (f->timestamp >> 16) & 0xFF;
packet[5] = (f->timestamp >> 8) & 0xFF;
packet[6] = (f->timestamp >> 0) & 0xFF;
packet[7] = (f->timestamp >> 24) & 0xFF;
packet[8] = 0;
packet[9] = 0;
packet[10] = 0;
// Video Header
packet[11] = (f->flags & CUTIHI_BS_FLAG_KEY) ? 0x17 : 0x27;
packet[12] = (f->flags & CUTIHI_BS_SETUP_PACKET) ? 0 : 1;
packet[13] = 0;
packet[14] = 0;
packet[15] = 0;
memcpy(&packet[16], avcc, avccSize);
packet[16 + avccSize + 0] = (tagSize >> 24) & 0xFF;
packet[16 + avccSize + 1] = (tagSize >> 16) & 0xFF;
packet[16 + avccSize + 2] = (tagSize >> 8) & 0xFF;
packet[16 + avccSize + 3] = (tagSize >> 0) & 0xFF;
RTMP_Write(n->rtmp, packet, rtmpPacketSize);
free(packet);
free(avcc);
}
return 1;
}
static int do_audio(Internal *n) {
if(!CHi_Crawl(&n->pub.sinks[1]) || !CHi_Crawl(&n->pub.sinks[1])->data.bitstream) {
return 1;
}
CHiBSFrames *frames = CHi_Crawl(&n->pub.sinks[1])->data.bitstream;
for(size_t fi = 0; fi < frames->count; fi++) {
CHiBSFrame *f = &frames->data[fi];
size_t avccSize = f->sz;
uint8_t *avcc = f->ptr;
size_t dataSize = FLV_AUDIO_HDR_SIZE + avccSize;
size_t tagSize = FLV_TAG_HEADER_SIZE + dataSize;
size_t rtmpPacketSize = tagSize + FLV_PREV_TAG_SIZE_SIZE;
uint8_t *packet = malloc(rtmpPacketSize);
// Tag
packet[0] = 8; // audio
packet[1] = (dataSize >> 16) & 0xFF;
packet[2] = (dataSize >> 8) & 0xFF;
packet[3] = (dataSize >> 0) & 0xFF;
packet[4] = (f->timestamp >> 16) & 0xFF;
packet[5] = (f->timestamp >> 8) & 0xFF;
packet[6] = (f->timestamp >> 0) & 0xFF;
packet[7] = (f->timestamp >> 24) & 0xFF;
packet[8] = 0;
packet[9] = 0;
packet[10] = 0;
// Audio Header
packet[11] = (1 << 0) | (1 << 1) | (3 << 2) | (10 << 4);
packet[12] = (f->flags & CUTIHI_BS_SETUP_PACKET) ? 0 : 1;
memcpy(&packet[13], avcc, avccSize);
packet[13 + avccSize + 0] = (tagSize >> 24) & 0xFF;
packet[13 + avccSize + 1] = (tagSize >> 16) & 0xFF;
packet[13 + avccSize + 2] = (tagSize >> 8) & 0xFF;
packet[13 + avccSize + 3] = (tagSize >> 0) & 0xFF;
RTMP_Write(n->rtmp, packet, rtmpPacketSize);
free(packet);
}
return 1;
}
static int streamrtmp_perform(CHiPubNode *pub) {
Internal *n = (Internal*) pub;
if(!do_video(n)) return 0;
if(!do_audio(n)) return 0;
int fd = RTMP_Socket(n->rtmp);
fd_set sockset;
struct timeval timeout = {};
FD_ZERO(&sockset);
FD_SET(fd, &sockset);
int result = select(fd + 1, &sockset, NULL, NULL, &timeout);
if(result == 1 && FD_ISSET(fd, &sockset)) {
RTMP_ReadPacket(n->rtmp, &n->rtmppkt);
if(!RTMPPacket_IsReady(&n->rtmppkt)) {
RTMP_ClientPacket(n->rtmp, &n->rtmppkt);
}
}
return 1;
}
CUTIVIS CHiPubNode *CHi_StreamRTMP() {
Internal *ret = calloc(1, sizeof(*ret));
ret->pub.type = CUTIHI_T('CStr', 'RTMP');
ret->pub.Start = streamrtmp_start;
ret->pub.Perform = streamrtmp_perform;
ret->pub.Stop = streamrtmp_stop;
ret->pub.sinks = calloc(sizeof(*ret->pub.sinks), ret->pub.sinkCount = 3);
ret->pub.sources = calloc(sizeof(*ret->pub.sources), ret->pub.sourceCount = 0);
return &ret->pub;
}