468 lines
12 KiB
C++
468 lines
12 KiB
C++
#include "sockutil.h"
|
|
#include "sys/pollfd.h"
|
|
#include "uri-parse.h"
|
|
#include "rtsp-media.h"
|
|
#include "rtp-profile.h"
|
|
#include "rtcp-header.h"
|
|
#include "rtsp-client.h"
|
|
#include "rtsp-demuxer.h"
|
|
#include "avpkt2bs.h"
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include "sockpair.h"
|
|
#include "cstringext.h"
|
|
#include "sys/system.h"
|
|
#include "cpm/unuse.h"
|
|
#include "time64.h"
|
|
#include "sdp.h"
|
|
|
|
#define PORT_RTSP 554
|
|
#define N_MEDIA 3
|
|
|
|
const struct rtsp_media_t* rtsp_client_get_media(struct rtsp_client_t* rtsp, int media);
|
|
|
|
typedef int (*rtsp_client_test2_handler)(struct rtsp_client_test2_t* ctx, socket_t socket, int media);
|
|
|
|
struct rtsp_client_test2_t
|
|
{
|
|
void* rtsp;
|
|
char buffer[4 * 1024];
|
|
socket_t socket;
|
|
|
|
int transport;
|
|
struct avpkt2bs_t bs;
|
|
|
|
int fds_count;
|
|
struct pollfd fds[1 + N_MEDIA * 2];
|
|
rtsp_client_test2_handler handlers[1 + N_MEDIA * 2];
|
|
|
|
int fds2media[N_MEDIA * 2];
|
|
struct rtsp_demuxer_t* demuxer[N_MEDIA];
|
|
};
|
|
|
|
static int rtsp_client_test2_onpacket(void* param, struct avpacket_t* pkt)
|
|
{
|
|
int r;
|
|
const uint8_t start_code[] = { 0, 0, 0, 1 };
|
|
struct rtsp_client_test2_t* ctx;
|
|
ctx = (struct rtsp_client_test2_t*)param;
|
|
|
|
printf("[RTSP] packet codec: %d, pts: %" PRId64 ", dts:%" PRId64 ",bytes: % d.\n", pkt->stream->codecid, pkt->pts, pkt->dts, pkt->size);
|
|
|
|
r = avpkt2bs_input(&ctx->bs, pkt);
|
|
if (r < 0)
|
|
{
|
|
printf("[RTSP] discard packet codec: %d, pts: %" PRId64 ", dts:%" PRId64 ",bytes: % d.\n", pkt->stream->codecid, pkt->pts, pkt->dts, pkt->size);
|
|
return 0; // discard
|
|
}
|
|
|
|
switch (pkt->stream->codecid)
|
|
{
|
|
case AVCODEC_AUDIO_AAC:
|
|
//aac_decode(aac, ctx->bs.ptr, r);
|
|
break;
|
|
|
|
case AVCODEC_VIDEO_H264:
|
|
//h264_decode(aac, ctx->bs.ptr, r);
|
|
break;
|
|
|
|
case AVCODEC_VIDEO_H265:
|
|
//h265_decode(aac, ctx->bs.ptr, r);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_run(struct rtsp_client_test2_t* ctx)
|
|
{
|
|
int i, r;
|
|
time64_t clock;
|
|
clock = time64_now();
|
|
while (1)
|
|
{
|
|
r = poll(ctx->fds, ctx->fds_count, 1000);
|
|
while (-1 == r && EINTR == errno)
|
|
r = poll(ctx->fds, ctx->fds_count, 1000);
|
|
|
|
if (0 == r)
|
|
{
|
|
continue; // timeout
|
|
}
|
|
else if (r < 0)
|
|
{
|
|
printf("[RTSP] poll read error: %d\n", r);
|
|
return r; // error
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < ctx->fds_count; i++)
|
|
{
|
|
if (0 != ctx->fds[i].revents)
|
|
{
|
|
r = ctx->handlers[i](ctx, ctx->fds[i].fd, ctx->fds2media[i]);
|
|
if (0 != r)
|
|
{
|
|
printf("[RTSP] media[%d], socket[%d] handle error: %d\n", ctx->fds2media[i], i, r);
|
|
}
|
|
|
|
ctx->fds[i].revents = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int rtsp_client_test2_onrtsp(struct rtsp_client_test2_t* ctx, socket_t socket, int media)
|
|
{
|
|
int r;
|
|
r = socket_recv(socket, ctx->buffer, sizeof(ctx->buffer), 0);
|
|
while (r > 0)
|
|
{
|
|
r = rtsp_client_input(ctx->rtsp, ctx->buffer, r);
|
|
if (0 != r)
|
|
break;
|
|
|
|
r = socket_recv(socket, ctx->buffer, sizeof(ctx->buffer), 0);
|
|
}
|
|
|
|
#if defined(OS_WINDOWS)
|
|
return r < 0 ? (socket_geterror() == WSAEWOULDBLOCK ? 0 : r) : 0;
|
|
#else
|
|
return r < 0 ? (errno == EAGAIN || errno == EWOULDBLOCK ? 0 : r) : 0;
|
|
#endif
|
|
}
|
|
|
|
static int rtsp_client_test2_onrtp(struct rtsp_client_test2_t* ctx, socket_t socket, int media)
|
|
{
|
|
int r;
|
|
socklen_t len;
|
|
struct sockaddr_storage ss;
|
|
|
|
len = sizeof(ss);
|
|
r = recvfrom(socket, ctx->buffer, sizeof(ctx->buffer), 0, (struct sockaddr*)&ss, &len);
|
|
if (r < 12)
|
|
{
|
|
printf("[RTSP] track(%d) recv error: %d\n", media, r);
|
|
return -1;
|
|
}
|
|
|
|
//assert(0 == socket_addr_compare((const struct sockaddr*)&ss, (const struct sockaddr*)&ctx->ss[0]));
|
|
|
|
if (media >= sizeof(ctx->demuxer) / sizeof(ctx->demuxer[0]) || ctx->demuxer[media] == NULL)
|
|
{
|
|
printf("[RTSP] track(%d) discard rtp packet\n", media);
|
|
return 0; // discard
|
|
}
|
|
r = rtsp_demuxer_input(ctx->demuxer[media], ctx->buffer, r);
|
|
return r < 0 ? r : 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_onrtcp(struct rtsp_client_test2_t* ctx, socket_t socket, int media)
|
|
{
|
|
int r;
|
|
socklen_t len;
|
|
struct sockaddr_storage ss;
|
|
|
|
len = sizeof(ss);
|
|
r = recvfrom(socket, ctx->buffer, sizeof(ctx->buffer), 0, (struct sockaddr*)&ss, &len);
|
|
if (r < 12)
|
|
{
|
|
printf("[RTSP] track(%d) recv error: %d\n", media, r);
|
|
return -1;
|
|
}
|
|
//assert(0 == socket_addr_compare((const struct sockaddr*)&ss, (const struct sockaddr*)&ctx->ss[1]));
|
|
|
|
if (media >= sizeof(ctx->demuxer) / sizeof(ctx->demuxer[0]) || ctx->demuxer[media] == NULL)
|
|
{
|
|
printf("[RTSP] track(%d) discard rtcp packet\n", media);
|
|
return 0; // discard
|
|
}
|
|
r = rtsp_demuxer_input(ctx->demuxer[media], ctx->buffer, r);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (RTCP_BYE == r)
|
|
{
|
|
printf("finished\n");
|
|
}
|
|
|
|
// RTCP report
|
|
r = rtsp_demuxer_rtcp(ctx->demuxer[media], ctx->buffer, sizeof(ctx->buffer));
|
|
if (r > 0)
|
|
r = socket_sendto(socket, ctx->buffer, r, 0, (const struct sockaddr*)&ss, len);
|
|
return r < 0 ? r : 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_ontcp(void* param, uint8_t channel, const void* data, uint16_t bytes)
|
|
{
|
|
int r, media;
|
|
struct rtsp_client_test2_t* ctx;
|
|
ctx = (struct rtsp_client_test2_t*)param;
|
|
|
|
media = channel / 2;
|
|
if (media >= sizeof(ctx->demuxer) / sizeof(ctx->demuxer[0]) || ctx->demuxer[media] == NULL)
|
|
{
|
|
printf("[RTSP] track(%d) discard rtp packet\n", media);
|
|
return 0; // discard
|
|
}
|
|
|
|
r = rtsp_demuxer_input(ctx->demuxer[media], data, bytes);
|
|
return r < 0 ? r : 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_send(void* param, const char* uri, const void* req, size_t bytes)
|
|
{
|
|
//TODO: check uri and make socket
|
|
//1. uri != rtsp describe uri(user input)
|
|
//2. multi-uri if media_count > 1
|
|
struct rtsp_client_test2_t* ctx = (struct rtsp_client_test2_t*)param;
|
|
return socket_send_all_by_time(ctx->socket, req, bytes, 0, 2000);
|
|
}
|
|
|
|
static int rtsp_client_test2_rtpport(void* param, int media, const char* source, unsigned short rtp[2], char* ip, int len)
|
|
{
|
|
int m, r;
|
|
socket_t socket[2];
|
|
struct rtsp_client_test2_t* ctx;
|
|
ctx = (struct rtsp_client_test2_t*)param;
|
|
|
|
m = rtsp_client_get_media_type(ctx->rtsp, media);
|
|
if (SDP_M_MEDIA_AUDIO != m && SDP_M_MEDIA_VIDEO != m)
|
|
return 0; // ignore
|
|
|
|
switch (ctx->transport)
|
|
{
|
|
case RTSP_TRANSPORT_RTP_UDP:
|
|
// TODO: ipv6
|
|
r = sockpair_create("0.0.0.0", socket, rtp);
|
|
if (0 != r)
|
|
return r;
|
|
|
|
// map fds to media index
|
|
ctx->fds2media[ctx->fds_count] = media;
|
|
ctx->fds2media[ctx->fds_count+1] = media;
|
|
|
|
ctx->fds[ctx->fds_count].fd = socket[0];
|
|
ctx->fds[ctx->fds_count].events = POLLIN;
|
|
ctx->fds[ctx->fds_count].revents = 0;
|
|
ctx->handlers[ctx->fds_count] = rtsp_client_test2_onrtp;
|
|
ctx->fds_count++;
|
|
|
|
ctx->fds[ctx->fds_count].fd = socket[1];
|
|
ctx->fds[ctx->fds_count].events = POLLIN;
|
|
ctx->fds[ctx->fds_count].revents = 0;
|
|
ctx->handlers[ctx->fds_count] = rtsp_client_test2_onrtcp;
|
|
ctx->fds_count++;
|
|
|
|
socket_setrecvbuf(rtp[0], 512 * 1024);
|
|
break;
|
|
|
|
case RTSP_TRANSPORT_RTP_TCP:
|
|
rtp[0] = 2 * media;
|
|
rtp[1] = 2 * media + 1;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
return -1;
|
|
}
|
|
|
|
return ctx->transport;
|
|
}
|
|
|
|
static int rtsp_client_test2_ondescribe(void* param, const char* sdp, int len)
|
|
{
|
|
struct rtsp_client_test2_t* ctx = (struct rtsp_client_test2_t*)param;
|
|
return rtsp_client_setup(ctx->rtsp, sdp, len);
|
|
}
|
|
|
|
static int rtsp_client_test2_onsetup(void* param, int timeout, int64_t duration)
|
|
{
|
|
int i, j, r;
|
|
uint64_t npt;
|
|
struct rtsp_client_test2_t* ctx;
|
|
const struct rtp_profile_t* profile;
|
|
ctx = (struct rtsp_client_test2_t*)param;
|
|
|
|
npt = 0;
|
|
r = rtsp_client_play(ctx->rtsp, &npt, NULL);
|
|
if (0 != r)
|
|
return r;
|
|
|
|
for (i = 0; i < rtsp_client_media_count(ctx->rtsp); i++)
|
|
{
|
|
int payload;
|
|
const char* encoding;
|
|
const struct rtsp_media_t* media;
|
|
const struct rtsp_header_transport_t* transport;
|
|
transport = rtsp_client_get_media_transport(ctx->rtsp, i);
|
|
encoding = rtsp_client_get_media_encoding(ctx->rtsp, i);
|
|
payload = rtsp_client_get_media_payload(ctx->rtsp, i);
|
|
media = rtsp_client_get_media(ctx->rtsp, i);
|
|
profile = rtp_profile_find(payload);
|
|
if (RTSP_TRANSPORT_RTP_UDP == transport->transport)
|
|
{
|
|
//assert(RTSP_TRANSPORT_RTP_UDP == transport->transport); // udp only
|
|
assert(0 == transport->multicast); // unicast only
|
|
//assert(transport->rtp.u.client_port1 == ctx->port[i][0]);
|
|
//assert(transport->rtp.u.client_port2 == ctx->port[i][1]);
|
|
//port[0] = transport->rtp.u.server_port1;
|
|
//port[1] = transport->rtp.u.server_port2;
|
|
//rtp_receiver_test(ctx->rtp[i], *transport->source ? transport->source : ctx->ip, port, payload, encoding);
|
|
|
|
//assert(0 == socket_addr_from(&ctx->ss[0], NULL, peer, (u_short)peerport[0]));
|
|
//assert(0 == socket_addr_from(&ctx->ss[1], NULL, peer, (u_short)peerport[1]));
|
|
//assert(0 == connect(rtp[0], (struct sockaddr*)&ctx->ss[0], len));
|
|
//assert(0 == connect(rtp[1], (struct sockaddr*)&ctx->ss[1], len));
|
|
}
|
|
else if (RTSP_TRANSPORT_RTP_TCP == transport->transport)
|
|
{
|
|
//assert(transport->rtp.u.client_port1 == transport->interleaved1);
|
|
//assert(transport->rtp.u.client_port2 == transport->interleaved2);
|
|
//rtp_receiver_tcp_test(transport->interleaved1, transport->interleaved2, payload, encoding);
|
|
}
|
|
else
|
|
{
|
|
assert(0); // TODO
|
|
}
|
|
|
|
ctx->demuxer[i] = rtsp_demuxer_create(i, 500, rtsp_client_test2_onpacket, ctx);
|
|
if (NULL == ctx->demuxer[i])
|
|
{
|
|
printf("[RTSP] rtsp_demuxer_create(%d, %s) error.\n", payload, encoding ? encoding : "");
|
|
return -1; // ignore
|
|
}
|
|
|
|
for (j = 0; j < media->avformat_count; j++)
|
|
{
|
|
r = rtsp_demuxer_add_payload(ctx->demuxer[i], media->avformats[j].rate, media->avformats[j].fmt, media->avformats[j].encoding, media->avformats[j].fmtp);
|
|
if (0 != r)
|
|
{
|
|
printf("RTSP] track[%d] add format [%d/%d/%s] failed.\n", i, media->avformats[j].rate, media->avformats[j].fmt, media->avformats[j].encoding);
|
|
continue; // ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_onteardown(void* param)
|
|
{
|
|
// todo
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_onplay(void* param, int media, const uint64_t* nptbegin, const uint64_t* nptend, const double* scale, const struct rtsp_rtp_info_t* rtpinfo, int count)
|
|
{
|
|
// todo
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_onpause(void* param)
|
|
{
|
|
// todo
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_create(struct rtsp_client_test2_t *ctx, const char* url, const char* username, const char* password, int transport)
|
|
{
|
|
struct rtsp_client_handler_t handler;
|
|
memset(&handler, 0, sizeof(handler));
|
|
handler.send = rtsp_client_test2_send;
|
|
handler.rtpport = rtsp_client_test2_rtpport;
|
|
handler.ondescribe = rtsp_client_test2_ondescribe;
|
|
handler.onsetup = rtsp_client_test2_onsetup;
|
|
handler.onplay = rtsp_client_test2_onplay;
|
|
handler.onpause = rtsp_client_test2_onpause;
|
|
handler.onteardown = rtsp_client_test2_onteardown;
|
|
handler.onrtp = rtsp_client_test2_ontcp;
|
|
|
|
ctx->rtsp = rtsp_client_create(url, username, password, &handler, ctx);
|
|
ctx->transport = 1 == transport ? RTSP_TRANSPORT_RTP_TCP : RTSP_TRANSPORT_RTP_UDP;
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_destroy(struct rtsp_client_test2_t* ctx)
|
|
{
|
|
if (ctx->rtsp)
|
|
rtsp_client_destroy(ctx->rtsp);
|
|
ctx->rtsp = NULL;
|
|
|
|
avpkt2bs_destroy(&ctx->bs);
|
|
return 0;
|
|
}
|
|
|
|
static int rtsp_client_test2_start(struct rtsp_client_test2_t* ctx, const char* url)
|
|
{
|
|
int r;
|
|
socket_t socket;
|
|
struct uri_t* uri;
|
|
|
|
uri = uri_parse(url, strlen(url));
|
|
if (!uri || !uri->host || !uri->scheme)
|
|
{
|
|
printf("[RTSP] uri [%s] parse error.\n", url);
|
|
return -1;
|
|
}
|
|
|
|
if (0 == uri->port)
|
|
uri->port = PORT_RTSP;
|
|
|
|
socket = socket_connect_host(uri->host, uri->port, 2000);
|
|
|
|
uri_free(uri);
|
|
if (socket_invalid == socket)
|
|
return -1;
|
|
|
|
ctx->socket = socket;
|
|
socket_setnonblock(socket, 1);
|
|
r = rtsp_client_describe(ctx->rtsp);
|
|
if (0 != r)
|
|
{
|
|
socket_close(socket);
|
|
return r;
|
|
}
|
|
|
|
ctx->fds_count = 1;
|
|
ctx->fds[0].fd = socket;
|
|
ctx->fds[0].events = POLLIN;
|
|
ctx->fds[0].revents = 0;
|
|
ctx->fds2media[0] = -1;
|
|
ctx->handlers[0] = rtsp_client_test2_onrtsp;
|
|
|
|
avpkt2bs_create(&ctx->bs);
|
|
|
|
r = rtsp_client_test2_run(ctx);
|
|
printf("[RTSP] exit with: %d\n", r);
|
|
|
|
socket_setnonblock(socket, 0);
|
|
r = rtsp_client_teardown(ctx->rtsp);
|
|
rtsp_client_destroy(ctx->rtsp);
|
|
socket_close(socket);
|
|
return 0;
|
|
}
|
|
|
|
void rtsp_client_test2(const char* url, const char* username, const char* password)
|
|
{
|
|
struct rtsp_client_test2_t ctx;
|
|
|
|
socket_init(); // optional
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
rtsp_client_test2_create(&ctx, url, username, password, 1 /*1-tcp, 0-udp*/);
|
|
rtsp_client_test2_start(&ctx, url);
|
|
rtsp_client_test2_destroy(&ctx);
|
|
|
|
socket_cleanup(); // optional
|
|
}
|