mirror of https://github.com/arendst/Tasmota.git
commit
bbdce8af29
|
@ -0,0 +1,412 @@
|
|||
#include "CRtspSession.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
CRtspSession::CRtspSession(SOCKET aRtspClient, CStreamer * aStreamer) : m_RtspClient(aRtspClient),m_Streamer(aStreamer)
|
||||
{
|
||||
printf("Creating RTSP session\n");
|
||||
Init();
|
||||
|
||||
m_RtspSessionID = getRandom(); // create a session ID
|
||||
m_RtspSessionID |= 0x80000000;
|
||||
m_StreamID = -1;
|
||||
m_ClientRTPPort = 0;
|
||||
m_ClientRTCPPort = 0;
|
||||
m_TcpTransport = false;
|
||||
m_streaming = false;
|
||||
m_stopped = false;
|
||||
};
|
||||
|
||||
CRtspSession::~CRtspSession()
|
||||
{
|
||||
closesocket(m_RtspClient);
|
||||
};
|
||||
|
||||
void CRtspSession::Init()
|
||||
{
|
||||
m_RtspCmdType = RTSP_UNKNOWN;
|
||||
memset(m_URLPreSuffix, 0x00, sizeof(m_URLPreSuffix));
|
||||
memset(m_URLSuffix, 0x00, sizeof(m_URLSuffix));
|
||||
memset(m_CSeq, 0x00, sizeof(m_CSeq));
|
||||
memset(m_URLHostPort, 0x00, sizeof(m_URLHostPort));
|
||||
m_ContentLength = 0;
|
||||
};
|
||||
|
||||
bool CRtspSession::ParseRtspRequest(char const * aRequest, unsigned aRequestSize)
|
||||
{
|
||||
char CmdName[RTSP_PARAM_STRING_MAX];
|
||||
static char CurRequest[RTSP_BUFFER_SIZE]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
|
||||
unsigned CurRequestSize;
|
||||
|
||||
Init();
|
||||
CurRequestSize = aRequestSize;
|
||||
memcpy(CurRequest,aRequest,aRequestSize);
|
||||
|
||||
// check whether the request contains information about the RTP/RTCP UDP client ports (SETUP command)
|
||||
char * ClientPortPtr;
|
||||
char * TmpPtr;
|
||||
static char CP[1024];
|
||||
char * pCP;
|
||||
|
||||
ClientPortPtr = strstr(CurRequest,"client_port");
|
||||
if (ClientPortPtr != nullptr)
|
||||
{
|
||||
TmpPtr = strstr(ClientPortPtr,"\r\n");
|
||||
if (TmpPtr != nullptr)
|
||||
{
|
||||
TmpPtr[0] = 0x00;
|
||||
strcpy(CP,ClientPortPtr);
|
||||
pCP = strstr(CP,"=");
|
||||
if (pCP != nullptr)
|
||||
{
|
||||
pCP++;
|
||||
strcpy(CP,pCP);
|
||||
pCP = strstr(CP,"-");
|
||||
if (pCP != nullptr)
|
||||
{
|
||||
pCP[0] = 0x00;
|
||||
m_ClientRTPPort = atoi(CP);
|
||||
m_ClientRTCPPort = m_ClientRTPPort + 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Read everything up to the first space as the command name
|
||||
bool parseSucceeded = false;
|
||||
unsigned i;
|
||||
for (i = 0; i < sizeof(CmdName)-1 && i < CurRequestSize; ++i)
|
||||
{
|
||||
char c = CurRequest[i];
|
||||
if (c == ' ' || c == '\t')
|
||||
{
|
||||
parseSucceeded = true;
|
||||
break;
|
||||
}
|
||||
CmdName[i] = c;
|
||||
}
|
||||
CmdName[i] = '\0';
|
||||
if (!parseSucceeded) {
|
||||
printf("failed to parse RTSP\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("RTSP received %s\n", CmdName);
|
||||
|
||||
// find out the command type
|
||||
if (strstr(CmdName,"OPTIONS") != nullptr) m_RtspCmdType = RTSP_OPTIONS; else
|
||||
if (strstr(CmdName,"DESCRIBE") != nullptr) m_RtspCmdType = RTSP_DESCRIBE; else
|
||||
if (strstr(CmdName,"SETUP") != nullptr) m_RtspCmdType = RTSP_SETUP; else
|
||||
if (strstr(CmdName,"PLAY") != nullptr) m_RtspCmdType = RTSP_PLAY; else
|
||||
if (strstr(CmdName,"TEARDOWN") != nullptr) m_RtspCmdType = RTSP_TEARDOWN;
|
||||
|
||||
// check whether the request contains transport information (UDP or TCP)
|
||||
if (m_RtspCmdType == RTSP_SETUP)
|
||||
{
|
||||
TmpPtr = strstr(CurRequest,"RTP/AVP/TCP");
|
||||
if (TmpPtr != nullptr) m_TcpTransport = true; else m_TcpTransport = false;
|
||||
};
|
||||
|
||||
// Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:
|
||||
unsigned j = i+1;
|
||||
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j; // skip over any additional white space
|
||||
for (; (int)j < (int)(CurRequestSize-8); ++j)
|
||||
{
|
||||
if ((CurRequest[j] == 'r' || CurRequest[j] == 'R') &&
|
||||
(CurRequest[j+1] == 't' || CurRequest[j+1] == 'T') &&
|
||||
(CurRequest[j+2] == 's' || CurRequest[j+2] == 'S') &&
|
||||
(CurRequest[j+3] == 'p' || CurRequest[j+3] == 'P') &&
|
||||
CurRequest[j+4] == ':' && CurRequest[j+5] == '/')
|
||||
{
|
||||
j += 6;
|
||||
if (CurRequest[j] == '/')
|
||||
{ // This is a "rtsp://" URL; skip over the host:port part that follows:
|
||||
++j;
|
||||
unsigned uidx = 0;
|
||||
while (j < CurRequestSize && CurRequest[j] != '/' && CurRequest[j] != ' ' && uidx < sizeof(m_URLHostPort) - 1)
|
||||
{ // extract the host:port part of the URL here
|
||||
m_URLHostPort[uidx] = CurRequest[j];
|
||||
uidx++;
|
||||
++j;
|
||||
};
|
||||
}
|
||||
else --j;
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the URL suffix (before the following "RTSP/"):
|
||||
parseSucceeded = false;
|
||||
for (unsigned k = i+1; (int)k < (int)(CurRequestSize-5); ++k)
|
||||
{
|
||||
if (CurRequest[k] == 'R' && CurRequest[k+1] == 'T' &&
|
||||
CurRequest[k+2] == 'S' && CurRequest[k+3] == 'P' &&
|
||||
CurRequest[k+4] == '/')
|
||||
{
|
||||
while (--k >= i && CurRequest[k] == ' ') {}
|
||||
unsigned k1 = k;
|
||||
while (k1 > i && CurRequest[k1] != '/') --k1;
|
||||
if (k - k1 + 1 > sizeof(m_URLSuffix)) return false;
|
||||
unsigned n = 0, k2 = k1+1;
|
||||
|
||||
while (k2 <= k) m_URLSuffix[n++] = CurRequest[k2++];
|
||||
m_URLSuffix[n] = '\0';
|
||||
|
||||
if (k1 - i > sizeof(m_URLPreSuffix)) return false;
|
||||
n = 0; k2 = i + 1;
|
||||
while (k2 <= k1 - 1) m_URLPreSuffix[n++] = CurRequest[k2++];
|
||||
m_URLPreSuffix[n] = '\0';
|
||||
i = k + 7;
|
||||
parseSucceeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parseSucceeded) return false;
|
||||
|
||||
// Look for "CSeq:", skip whitespace, then read everything up to the next \r or \n as 'CSeq':
|
||||
parseSucceeded = false;
|
||||
for (j = i; (int)j < (int)(CurRequestSize-5); ++j)
|
||||
{
|
||||
if (CurRequest[j] == 'C' && CurRequest[j+1] == 'S' &&
|
||||
CurRequest[j+2] == 'e' && CurRequest[j+3] == 'q' &&
|
||||
CurRequest[j+4] == ':')
|
||||
{
|
||||
j += 5;
|
||||
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j;
|
||||
unsigned n;
|
||||
for (n = 0; n < sizeof(m_CSeq)-1 && j < CurRequestSize; ++n,++j)
|
||||
{
|
||||
char c = CurRequest[j];
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
parseSucceeded = true;
|
||||
break;
|
||||
}
|
||||
m_CSeq[n] = c;
|
||||
}
|
||||
m_CSeq[n] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parseSucceeded) return false;
|
||||
|
||||
// Also: Look for "Content-Length:" (optional)
|
||||
for (j = i; (int)j < (int)(CurRequestSize-15); ++j)
|
||||
{
|
||||
if (CurRequest[j] == 'C' && CurRequest[j+1] == 'o' &&
|
||||
CurRequest[j+2] == 'n' && CurRequest[j+3] == 't' &&
|
||||
CurRequest[j+4] == 'e' && CurRequest[j+5] == 'n' &&
|
||||
CurRequest[j+6] == 't' && CurRequest[j+7] == '-' &&
|
||||
(CurRequest[j+8] == 'L' || CurRequest[j+8] == 'l') &&
|
||||
CurRequest[j+9] == 'e' && CurRequest[j+10] == 'n' &&
|
||||
CurRequest[j+11] == 'g' && CurRequest[j+12] == 't' &&
|
||||
CurRequest[j+13] == 'h' && CurRequest[j+14] == ':')
|
||||
{
|
||||
j += 15;
|
||||
while (j < CurRequestSize && (CurRequest[j] == ' ' || CurRequest[j] == '\t')) ++j;
|
||||
unsigned num;
|
||||
if (sscanf(&CurRequest[j], "%u", &num) == 1) m_ContentLength = num;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
RTSP_CMD_TYPES CRtspSession::Handle_RtspRequest(char const * aRequest, unsigned aRequestSize)
|
||||
{
|
||||
if (ParseRtspRequest(aRequest,aRequestSize))
|
||||
{
|
||||
switch (m_RtspCmdType)
|
||||
{
|
||||
case RTSP_OPTIONS: { Handle_RtspOPTION(); break; };
|
||||
case RTSP_DESCRIBE: { Handle_RtspDESCRIBE(); break; };
|
||||
case RTSP_SETUP: { Handle_RtspSETUP(); break; };
|
||||
case RTSP_PLAY: { Handle_RtspPLAY(); break; };
|
||||
default: {};
|
||||
};
|
||||
};
|
||||
return m_RtspCmdType;
|
||||
};
|
||||
|
||||
void CRtspSession::Handle_RtspOPTION()
|
||||
{
|
||||
static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
|
||||
|
||||
snprintf(Response,sizeof(Response),
|
||||
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
|
||||
"Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n\r\n",m_CSeq);
|
||||
|
||||
socketsend(m_RtspClient,Response,strlen(Response));
|
||||
}
|
||||
|
||||
void CRtspSession::Handle_RtspDESCRIBE()
|
||||
{
|
||||
static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
|
||||
static char SDPBuf[1024];
|
||||
static char URLBuf[1024];
|
||||
|
||||
// check whether we know a stream with the URL which is requested
|
||||
m_StreamID = -1; // invalid URL
|
||||
if ((strcmp(m_URLPreSuffix,"mjpeg") == 0) && (strcmp(m_URLSuffix,"1") == 0)) m_StreamID = 0; else
|
||||
if ((strcmp(m_URLPreSuffix,"mjpeg") == 0) && (strcmp(m_URLSuffix,"2") == 0)) m_StreamID = 1;
|
||||
if (m_StreamID == -1)
|
||||
{ // Stream not available
|
||||
snprintf(Response,sizeof(Response),
|
||||
"RTSP/1.0 404 Stream Not Found\r\nCSeq: %s\r\n%s\r\n",
|
||||
m_CSeq,
|
||||
DateHeader());
|
||||
|
||||
socketsend(m_RtspClient,Response,strlen(Response));
|
||||
return;
|
||||
};
|
||||
|
||||
// simulate DESCRIBE server response
|
||||
static char OBuf[256];
|
||||
char * ColonPtr;
|
||||
strcpy(OBuf,m_URLHostPort);
|
||||
ColonPtr = strstr(OBuf,":");
|
||||
if (ColonPtr != nullptr) ColonPtr[0] = 0x00;
|
||||
|
||||
snprintf(SDPBuf,sizeof(SDPBuf),
|
||||
"v=0\r\n"
|
||||
"o=- %d 1 IN IP4 %s\r\n"
|
||||
"s=\r\n"
|
||||
"t=0 0\r\n" // start / stop - 0 -> unbounded and permanent session
|
||||
"m=video 0 RTP/AVP 26\r\n" // currently we just handle UDP sessions
|
||||
// "a=x-dimensions: 640,480\r\n"
|
||||
"c=IN IP4 0.0.0.0\r\n",
|
||||
rand(),
|
||||
OBuf);
|
||||
char StreamName[64];
|
||||
switch (m_StreamID)
|
||||
{
|
||||
case 0: strcpy(StreamName,"mjpeg/1"); break;
|
||||
case 1: strcpy(StreamName,"mjpeg/2"); break;
|
||||
};
|
||||
snprintf(URLBuf,sizeof(URLBuf),
|
||||
"rtsp://%s/%s",
|
||||
m_URLHostPort,
|
||||
StreamName);
|
||||
snprintf(Response,sizeof(Response),
|
||||
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
|
||||
"%s\r\n"
|
||||
"Content-Base: %s/\r\n"
|
||||
"Content-Type: application/sdp\r\n"
|
||||
"Content-Length: %d\r\n\r\n"
|
||||
"%s",
|
||||
m_CSeq,
|
||||
DateHeader(),
|
||||
URLBuf,
|
||||
(int) strlen(SDPBuf),
|
||||
SDPBuf);
|
||||
|
||||
socketsend(m_RtspClient,Response,strlen(Response));
|
||||
}
|
||||
|
||||
void CRtspSession::Handle_RtspSETUP()
|
||||
{
|
||||
static char Response[1024];
|
||||
static char Transport[255];
|
||||
|
||||
// init RTP streamer transport type (UDP or TCP) and ports for UDP transport
|
||||
m_Streamer->InitTransport(m_ClientRTPPort,m_ClientRTCPPort,m_TcpTransport);
|
||||
|
||||
// simulate SETUP server response
|
||||
if (m_TcpTransport)
|
||||
snprintf(Transport,sizeof(Transport),"RTP/AVP/TCP;unicast;interleaved=0-1");
|
||||
else
|
||||
snprintf(Transport,sizeof(Transport),
|
||||
"RTP/AVP;unicast;destination=127.0.0.1;source=127.0.0.1;client_port=%i-%i;server_port=%i-%i",
|
||||
m_ClientRTPPort,
|
||||
m_ClientRTCPPort,
|
||||
m_Streamer->GetRtpServerPort(),
|
||||
m_Streamer->GetRtcpServerPort());
|
||||
snprintf(Response,sizeof(Response),
|
||||
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
|
||||
"%s\r\n"
|
||||
"Transport: %s\r\n"
|
||||
"Session: %i\r\n\r\n",
|
||||
m_CSeq,
|
||||
DateHeader(),
|
||||
Transport,
|
||||
m_RtspSessionID);
|
||||
|
||||
socketsend(m_RtspClient,Response,strlen(Response));
|
||||
}
|
||||
|
||||
void CRtspSession::Handle_RtspPLAY()
|
||||
{
|
||||
static char Response[1024];
|
||||
|
||||
// simulate SETUP server response
|
||||
snprintf(Response,sizeof(Response),
|
||||
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
|
||||
"%s\r\n"
|
||||
"Range: npt=0.000-\r\n"
|
||||
"Session: %i\r\n"
|
||||
"RTP-Info: url=rtsp://127.0.0.1:8554/mjpeg/1/track1\r\n\r\n",
|
||||
m_CSeq,
|
||||
DateHeader(),
|
||||
m_RtspSessionID);
|
||||
|
||||
socketsend(m_RtspClient,Response,strlen(Response));
|
||||
}
|
||||
|
||||
char const * CRtspSession::DateHeader()
|
||||
{
|
||||
static char buf[200];
|
||||
time_t tt = time(NULL);
|
||||
strftime(buf, sizeof buf, "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
|
||||
return buf;
|
||||
}
|
||||
|
||||
int CRtspSession::GetStreamID()
|
||||
{
|
||||
return m_StreamID;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Read from our socket, parsing commands as possible.
|
||||
*/
|
||||
bool CRtspSession::handleRequests(uint32_t readTimeoutMs)
|
||||
{
|
||||
if(m_stopped)
|
||||
return false; // Already closed down
|
||||
|
||||
static char RecvBuf[RTSP_BUFFER_SIZE]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
|
||||
|
||||
memset(RecvBuf,0x00,sizeof(RecvBuf));
|
||||
int res = socketread(m_RtspClient,RecvBuf,sizeof(RecvBuf), readTimeoutMs);
|
||||
if(res > 0) {
|
||||
// we filter away everything which seems not to be an RTSP command: O-ption, D-escribe, S-etup, P-lay, T-eardown
|
||||
if ((RecvBuf[0] == 'O') || (RecvBuf[0] == 'D') || (RecvBuf[0] == 'S') || (RecvBuf[0] == 'P') || (RecvBuf[0] == 'T'))
|
||||
{
|
||||
RTSP_CMD_TYPES C = Handle_RtspRequest(RecvBuf,res);
|
||||
if (C == RTSP_PLAY)
|
||||
m_streaming = true;
|
||||
else if (C == RTSP_TEARDOWN)
|
||||
m_stopped = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(res == 0) {
|
||||
printf("client closed socket, exiting\n");
|
||||
m_stopped = true;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Timeout on read
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CRtspSession::broadcastCurrentFrame(uint32_t curMsec) {
|
||||
// Send a frame
|
||||
if (m_streaming && !m_stopped) {
|
||||
// printf("serving a frame\n");
|
||||
m_Streamer->streamImage(curMsec);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include "CStreamer.h"
|
||||
#include "platglue.h"
|
||||
|
||||
// supported command types
|
||||
enum RTSP_CMD_TYPES
|
||||
{
|
||||
RTSP_OPTIONS,
|
||||
RTSP_DESCRIBE,
|
||||
RTSP_SETUP,
|
||||
RTSP_PLAY,
|
||||
RTSP_TEARDOWN,
|
||||
RTSP_UNKNOWN
|
||||
};
|
||||
|
||||
#define RTSP_BUFFER_SIZE 10000 // for incoming requests, and outgoing responses
|
||||
#define RTSP_PARAM_STRING_MAX 200
|
||||
#define MAX_HOSTNAME_LEN 256
|
||||
|
||||
class CRtspSession
|
||||
{
|
||||
public:
|
||||
CRtspSession(SOCKET aRtspClient, CStreamer * aStreamer);
|
||||
~CRtspSession();
|
||||
|
||||
RTSP_CMD_TYPES Handle_RtspRequest(char const * aRequest, unsigned aRequestSize);
|
||||
int GetStreamID();
|
||||
|
||||
/**
|
||||
Read from our socket, parsing commands as possible.
|
||||
|
||||
return false if the read timed out
|
||||
*/
|
||||
bool handleRequests(uint32_t readTimeoutMs);
|
||||
|
||||
/**
|
||||
broadcast a current frame
|
||||
*/
|
||||
void broadcastCurrentFrame(uint32_t curMsec);
|
||||
|
||||
bool m_streaming;
|
||||
bool m_stopped;
|
||||
|
||||
private:
|
||||
void Init();
|
||||
bool ParseRtspRequest(char const * aRequest, unsigned aRequestSize);
|
||||
char const * DateHeader();
|
||||
|
||||
// RTSP request command handlers
|
||||
void Handle_RtspOPTION();
|
||||
void Handle_RtspDESCRIBE();
|
||||
void Handle_RtspSETUP();
|
||||
void Handle_RtspPLAY();
|
||||
|
||||
// global session state parameters
|
||||
int m_RtspSessionID;
|
||||
SOCKET m_RtspClient; // RTSP socket of that session
|
||||
int m_StreamID; // number of simulated stream of that session
|
||||
IPPORT m_ClientRTPPort; // client port for UDP based RTP transport
|
||||
IPPORT m_ClientRTCPPort; // client port for UDP based RTCP transport
|
||||
bool m_TcpTransport; // if Tcp based streaming was activated
|
||||
CStreamer * m_Streamer; // the UDP or TCP streamer of that session
|
||||
|
||||
// parameters of the last received RTSP request
|
||||
|
||||
RTSP_CMD_TYPES m_RtspCmdType; // command type (if any) of the current request
|
||||
char m_URLPreSuffix[RTSP_PARAM_STRING_MAX]; // stream name pre suffix
|
||||
char m_URLSuffix[RTSP_PARAM_STRING_MAX]; // stream name suffix
|
||||
char m_CSeq[RTSP_PARAM_STRING_MAX]; // RTSP command sequence number
|
||||
char m_URLHostPort[MAX_HOSTNAME_LEN]; // host:port part of the URL
|
||||
unsigned m_ContentLength; // SDP string size
|
||||
};
|
|
@ -0,0 +1,350 @@
|
|||
#include "CStreamer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
//#define STREAM_DEBUG
|
||||
|
||||
CStreamer::CStreamer(SOCKET aClient, u_short width, u_short height) : m_Client(aClient)
|
||||
{
|
||||
printf("Creating TSP streamer\n");
|
||||
m_RtpServerPort = 0;
|
||||
m_RtcpServerPort = 0;
|
||||
m_RtpClientPort = 0;
|
||||
m_RtcpClientPort = 0;
|
||||
|
||||
m_SequenceNumber = 0;
|
||||
m_Timestamp = 0;
|
||||
m_SendIdx = 0;
|
||||
m_TCPTransport = false;
|
||||
|
||||
m_RtpSocket = NULLSOCKET;
|
||||
m_RtcpSocket = NULLSOCKET;
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_prevMsec = 0;
|
||||
};
|
||||
|
||||
CStreamer::~CStreamer()
|
||||
{
|
||||
udpsocketclose(m_RtpSocket);
|
||||
udpsocketclose(m_RtcpSocket);
|
||||
};
|
||||
|
||||
int CStreamer::SendRtpPacket(unsigned const char * jpeg, int jpegLen, int fragmentOffset, BufPtr quant0tbl, BufPtr quant1tbl)
|
||||
{
|
||||
#define KRtpHeaderSize 12 // size of the RTP header
|
||||
#define KJpegHeaderSize 8 // size of the special JPEG payload header
|
||||
|
||||
#define MAX_FRAGMENT_SIZE 1100 // FIXME, pick more carefully
|
||||
int fragmentLen = MAX_FRAGMENT_SIZE;
|
||||
if(fragmentLen + fragmentOffset > jpegLen) // Shrink last fragment if needed
|
||||
fragmentLen = jpegLen - fragmentOffset;
|
||||
|
||||
bool isLastFragment = (fragmentOffset + fragmentLen) == jpegLen;
|
||||
|
||||
// Do we have custom quant tables? If so include them per RFC
|
||||
|
||||
bool includeQuantTbl = quant0tbl && quant1tbl && fragmentOffset == 0;
|
||||
uint8_t q = includeQuantTbl ? 128 : 0x5e;
|
||||
|
||||
static char RtpBuf[2048]; // Note: we assume single threaded, this large buf we keep off of the tiny stack
|
||||
int RtpPacketSize = fragmentLen + KRtpHeaderSize + KJpegHeaderSize + (includeQuantTbl ? (4 + 64 * 2) : 0);
|
||||
|
||||
memset(RtpBuf,0x00,sizeof(RtpBuf));
|
||||
// Prepare the first 4 byte of the packet. This is the Rtp over Rtsp header in case of TCP based transport
|
||||
RtpBuf[0] = '$'; // magic number
|
||||
RtpBuf[1] = 0; // number of multiplexed subchannel on RTPS connection - here the RTP channel
|
||||
RtpBuf[2] = (RtpPacketSize & 0x0000FF00) >> 8;
|
||||
RtpBuf[3] = (RtpPacketSize & 0x000000FF);
|
||||
// Prepare the 12 byte RTP header
|
||||
RtpBuf[4] = 0x80; // RTP version
|
||||
RtpBuf[5] = 0x1a | (isLastFragment ? 0x80 : 0x00); // JPEG payload (26) and marker bit
|
||||
RtpBuf[7] = m_SequenceNumber & 0x0FF; // each packet is counted with a sequence counter
|
||||
RtpBuf[6] = m_SequenceNumber >> 8;
|
||||
RtpBuf[8] = (m_Timestamp & 0xFF000000) >> 24; // each image gets a timestamp
|
||||
RtpBuf[9] = (m_Timestamp & 0x00FF0000) >> 16;
|
||||
RtpBuf[10] = (m_Timestamp & 0x0000FF00) >> 8;
|
||||
RtpBuf[11] = (m_Timestamp & 0x000000FF);
|
||||
RtpBuf[12] = 0x13; // 4 byte SSRC (sychronization source identifier)
|
||||
RtpBuf[13] = 0xf9; // we just an arbitrary number here to keep it simple
|
||||
RtpBuf[14] = 0x7e;
|
||||
RtpBuf[15] = 0x67;
|
||||
|
||||
// Prepare the 8 byte payload JPEG header
|
||||
RtpBuf[16] = 0x00; // type specific
|
||||
RtpBuf[17] = (fragmentOffset & 0x00FF0000) >> 16; // 3 byte fragmentation offset for fragmented images
|
||||
RtpBuf[18] = (fragmentOffset & 0x0000FF00) >> 8;
|
||||
RtpBuf[19] = (fragmentOffset & 0x000000FF);
|
||||
|
||||
/* These sampling factors indicate that the chrominance components of
|
||||
type 0 video is downsampled horizontally by 2 (often called 4:2:2)
|
||||
while the chrominance components of type 1 video are downsampled both
|
||||
horizontally and vertically by 2 (often called 4:2:0). */
|
||||
RtpBuf[20] = 0x00; // type (fixme might be wrong for camera data) https://tools.ietf.org/html/rfc2435
|
||||
RtpBuf[21] = q; // quality scale factor was 0x5e
|
||||
RtpBuf[22] = m_width / 8; // width / 8
|
||||
RtpBuf[23] = m_height / 8; // height / 8
|
||||
|
||||
int headerLen = 24; // Inlcuding jpeg header but not qant table header
|
||||
if(includeQuantTbl) { // we need a quant header - but only in first packet of the frame
|
||||
//printf("inserting quanttbl\n");
|
||||
RtpBuf[24] = 0; // MBZ
|
||||
RtpBuf[25] = 0; // 8 bit precision
|
||||
RtpBuf[26] = 0; // MSB of lentgh
|
||||
|
||||
int numQantBytes = 64; // Two 64 byte tables
|
||||
RtpBuf[27] = 2 * numQantBytes; // LSB of length
|
||||
|
||||
headerLen += 4;
|
||||
|
||||
memcpy(RtpBuf + headerLen, quant0tbl, numQantBytes);
|
||||
headerLen += numQantBytes;
|
||||
|
||||
memcpy(RtpBuf + headerLen, quant1tbl, numQantBytes);
|
||||
headerLen += numQantBytes;
|
||||
}
|
||||
// printf("Sending timestamp %d, seq %d, fragoff %d, fraglen %d, jpegLen %d\n", m_Timestamp, m_SequenceNumber, fragmentOffset, fragmentLen, jpegLen);
|
||||
|
||||
// append the JPEG scan data to the RTP buffer
|
||||
memcpy(RtpBuf + headerLen,jpeg + fragmentOffset, fragmentLen);
|
||||
fragmentOffset += fragmentLen;
|
||||
|
||||
m_SequenceNumber++; // prepare the packet counter for the next packet
|
||||
|
||||
IPADDRESS otherip;
|
||||
IPPORT otherport;
|
||||
socketpeeraddr(m_Client, &otherip, &otherport);
|
||||
|
||||
// RTP marker bit must be set on last fragment
|
||||
if (m_TCPTransport) // RTP over RTSP - we send the buffer + 4 byte additional header
|
||||
socketsend(m_Client,RtpBuf,RtpPacketSize + 4);
|
||||
else // UDP - we send just the buffer by skipping the 4 byte RTP over RTSP header
|
||||
udpsocketsend(m_RtpSocket,&RtpBuf[4],RtpPacketSize, otherip, m_RtpClientPort);
|
||||
|
||||
return isLastFragment ? 0 : fragmentOffset;
|
||||
};
|
||||
|
||||
void CStreamer::InitTransport(u_short aRtpPort, u_short aRtcpPort, bool TCP)
|
||||
{
|
||||
m_RtpClientPort = aRtpPort;
|
||||
m_RtcpClientPort = aRtcpPort;
|
||||
m_TCPTransport = TCP;
|
||||
|
||||
if (!m_TCPTransport)
|
||||
{ // allocate port pairs for RTP/RTCP ports in UDP transport mode
|
||||
for (u_short P = 6970; P < 0xFFFE; P += 2)
|
||||
{
|
||||
m_RtpSocket = udpsocketcreate(P);
|
||||
if (m_RtpSocket)
|
||||
{ // Rtp socket was bound successfully. Lets try to bind the consecutive Rtsp socket
|
||||
m_RtcpSocket = udpsocketcreate(P + 1);
|
||||
if (m_RtcpSocket)
|
||||
{
|
||||
m_RtpServerPort = P;
|
||||
m_RtcpServerPort = P+1;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
udpsocketclose(m_RtpSocket);
|
||||
udpsocketclose(m_RtcpSocket);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
u_short CStreamer::GetRtpServerPort()
|
||||
{
|
||||
return m_RtpServerPort;
|
||||
};
|
||||
|
||||
u_short CStreamer::GetRtcpServerPort()
|
||||
{
|
||||
return m_RtcpServerPort;
|
||||
};
|
||||
|
||||
void CStreamer::streamFrame(unsigned const char *data, uint32_t dataLen, uint32_t curMsec)
|
||||
{
|
||||
if(m_prevMsec == 0) // first frame init our timestamp
|
||||
m_prevMsec = curMsec;
|
||||
|
||||
// compute deltat (being careful to handle clock rollover with a little lie)
|
||||
uint32_t deltams = (curMsec >= m_prevMsec) ? curMsec - m_prevMsec : 100;
|
||||
m_prevMsec = curMsec;
|
||||
|
||||
// locate quant tables if possible
|
||||
BufPtr qtable0, qtable1;
|
||||
|
||||
if (!decodeJPEGfile(&data, &dataLen, &qtable0, &qtable1)) {
|
||||
#ifdef STREAM_DEBUG
|
||||
printf("can't decode jpeg data\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
do {
|
||||
offset = SendRtpPacket(data, dataLen, offset, qtable0, qtable1);
|
||||
} while(offset != 0);
|
||||
|
||||
// Increment ONLY after a full frame
|
||||
uint32_t units = 90000; // Hz per RFC 2435
|
||||
m_Timestamp += (units * deltams / 1000); // fixed timestamp increment for a frame rate of 25fps
|
||||
|
||||
m_SendIdx++;
|
||||
if (m_SendIdx > 1) m_SendIdx = 0;
|
||||
|
||||
#ifdef STREAM_DEBUG
|
||||
printf("frame sent\n");
|
||||
#endif
|
||||
};
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// search for a particular JPEG marker, moves *start to just after that marker
|
||||
// This function fixes up the provided start ptr to point to the
|
||||
// actual JPEG stream data and returns the number of bytes skipped
|
||||
// APP0 e0
|
||||
// DQT db
|
||||
// DQT db
|
||||
// DHT c4
|
||||
// DHT c4
|
||||
// DHT c4
|
||||
// DHT c4
|
||||
// SOF0 c0 baseline (not progressive) 3 color 0x01 Y, 0x21 2h1v, 0x00 tbl0
|
||||
// - 0x02 Cb, 0x11 1h1v, 0x01 tbl1 - 0x03 Cr, 0x11 1h1v, 0x01 tbl1
|
||||
// therefore 4:2:2, with two separate quant tables (0 and 1)
|
||||
// SOS da
|
||||
// EOI d9 (no need to strip data after this RFC says client will discard)
|
||||
bool findJPEGheader(BufPtr *start, uint32_t *len, uint8_t marker) {
|
||||
// per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
|
||||
unsigned const char *bytes = *start;
|
||||
|
||||
// kinda skanky, will break if unlucky and the headers inxlucde 0xffda
|
||||
// might fall off array if jpeg is invalid
|
||||
// FIXME - return false instead
|
||||
while(bytes - *start < *len) {
|
||||
uint8_t framing = *bytes++; // better be 0xff
|
||||
if(framing != 0xff) {
|
||||
printf("malformed jpeg, framing=%x\n", framing);
|
||||
return false;
|
||||
}
|
||||
uint8_t typecode = *bytes++;
|
||||
if(typecode == marker) {
|
||||
unsigned skipped = bytes - *start;
|
||||
//printf("found marker 0x%x, skipped %d\n", marker, skipped);
|
||||
|
||||
*start = bytes;
|
||||
|
||||
// shrink len for the bytes we just skipped
|
||||
*len -= skipped;
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// not the section we were looking for, skip the entire section
|
||||
switch(typecode) {
|
||||
case 0xd8: // start of image
|
||||
{
|
||||
break; // no data to skip
|
||||
}
|
||||
case 0xe0: // app0
|
||||
case 0xdb: // dqt
|
||||
case 0xc4: // dht
|
||||
case 0xc0: // sof0
|
||||
case 0xda: // sos
|
||||
{
|
||||
// standard format section with 2 bytes for len. skip that many bytes
|
||||
uint32_t len = bytes[0] * 256 + bytes[1];
|
||||
//printf("skipping section 0x%x, %d bytes\n", typecode, len);
|
||||
bytes += len;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
printf("unexpected jpeg typecode 0x%x\n", typecode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("failed to find jpeg marker 0x%x", marker);
|
||||
return false;
|
||||
}
|
||||
|
||||
// the scan data uses byte stuffing to guarantee anything that starts with 0xff
|
||||
// followed by something not zero, is a new section. Look for that marker and return the ptr
|
||||
// pointing there
|
||||
void skipScanBytes(BufPtr *start) {
|
||||
BufPtr bytes = *start;
|
||||
|
||||
while(true) { // FIXME, check against length
|
||||
while(*bytes++ != 0xff);
|
||||
if(*bytes++ != 0) {
|
||||
*start = bytes - 2; // back up to the 0xff marker we just found
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void nextJpegBlock(BufPtr *bytes) {
|
||||
uint32_t len = (*bytes)[0] * 256 + (*bytes)[1];
|
||||
//printf("going to next jpeg block %d bytes\n", len);
|
||||
*bytes += len;
|
||||
}
|
||||
|
||||
// When JPEG is stored as a file it is wrapped in a container
|
||||
// This function fixes up the provided start ptr to point to the
|
||||
// actual JPEG stream data and returns the number of bytes skipped
|
||||
bool decodeJPEGfile(BufPtr *start, uint32_t *len, BufPtr *qtable0, BufPtr *qtable1) {
|
||||
// per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
|
||||
unsigned const char *bytes = *start;
|
||||
|
||||
if(!findJPEGheader(&bytes, len, 0xd8)) // better at least look like a jpeg file
|
||||
return false; // FAILED!
|
||||
|
||||
// Look for quant tables if they are present
|
||||
*qtable0 = NULL;
|
||||
*qtable1 = NULL;
|
||||
BufPtr quantstart = *start;
|
||||
uint32_t quantlen = *len;
|
||||
if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) {
|
||||
printf("error can't find quant table 0\n");
|
||||
}
|
||||
else {
|
||||
// printf("found quant table %x\n", quantstart[2]);
|
||||
|
||||
*qtable0 = quantstart + 3; // 3 bytes of header skipped
|
||||
nextJpegBlock(&quantstart);
|
||||
if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) {
|
||||
printf("error can't find quant table 1\n");
|
||||
}
|
||||
else {
|
||||
// printf("found quant table %x\n", quantstart[2]);
|
||||
}
|
||||
*qtable1 = quantstart + 3;
|
||||
nextJpegBlock(&quantstart);
|
||||
}
|
||||
|
||||
if(!findJPEGheader(start, len, 0xda))
|
||||
return false; // FAILED!
|
||||
|
||||
// Skip the header bytes of the SOS marker FIXME why doesn't this work?
|
||||
uint32_t soslen = (*start)[0] * 256 + (*start)[1];
|
||||
*start += soslen;
|
||||
*len -= soslen;
|
||||
|
||||
// start scanning the data portion of the scan to find the end marker
|
||||
BufPtr endmarkerptr = *start;
|
||||
uint32_t endlen = *len;
|
||||
|
||||
skipScanBytes(&endmarkerptr);
|
||||
if(!findJPEGheader(&endmarkerptr, &endlen, 0xd9))
|
||||
return false; // FAILED!
|
||||
|
||||
// endlen must now be the # of bytes between the start of our scan and
|
||||
// the end marker, tell the caller to ignore bytes afterwards
|
||||
*len = endmarkerptr - *start;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "platglue.h"
|
||||
|
||||
typedef unsigned const char *BufPtr;
|
||||
|
||||
class CStreamer
|
||||
{
|
||||
public:
|
||||
CStreamer(SOCKET aClient, u_short width, u_short height);
|
||||
virtual ~CStreamer();
|
||||
|
||||
void InitTransport(u_short aRtpPort, u_short aRtcpPort, bool TCP);
|
||||
u_short GetRtpServerPort();
|
||||
u_short GetRtcpServerPort();
|
||||
|
||||
virtual void streamImage(uint32_t curMsec) = 0; // send a new image to the client
|
||||
protected:
|
||||
|
||||
void streamFrame(unsigned const char *data, uint32_t dataLen, uint32_t curMsec);
|
||||
|
||||
private:
|
||||
int SendRtpPacket(unsigned const char *jpeg, int jpegLen, int fragmentOffset, BufPtr quant0tbl = NULL, BufPtr quant1tbl = NULL);// returns new fragmentOffset or 0 if finished with frame
|
||||
|
||||
UDPSOCKET m_RtpSocket; // RTP socket for streaming RTP packets to client
|
||||
UDPSOCKET m_RtcpSocket; // RTCP socket for sending/receiving RTCP packages
|
||||
|
||||
uint16_t m_RtpClientPort; // RTP receiver port on client (in host byte order!)
|
||||
uint16_t m_RtcpClientPort; // RTCP receiver port on client (in host byte order!)
|
||||
IPPORT m_RtpServerPort; // RTP sender port on server
|
||||
IPPORT m_RtcpServerPort; // RTCP sender port on server
|
||||
|
||||
u_short m_SequenceNumber;
|
||||
uint32_t m_Timestamp;
|
||||
int m_SendIdx;
|
||||
bool m_TCPTransport;
|
||||
SOCKET m_Client;
|
||||
uint32_t m_prevMsec;
|
||||
|
||||
u_short m_width; // image data info
|
||||
u_short m_height;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// When JPEG is stored as a file it is wrapped in a container
|
||||
// This function fixes up the provided start ptr to point to the
|
||||
// actual JPEG stream data and returns the number of bytes skipped
|
||||
// returns true if the file seems to be valid jpeg
|
||||
// If quant tables can be found they will be stored in qtable0/1
|
||||
bool decodeJPEGfile(BufPtr *start, uint32_t *len, BufPtr *qtable0, BufPtr *qtable1);
|
||||
bool findJPEGheader(BufPtr *start, uint32_t *len, uint8_t marker);
|
||||
|
||||
// Given a jpeg ptr pointing to a pair of length bytes, advance the pointer to
|
||||
// the next 0xff marker byte
|
||||
void nextJpegBlock(BufPtr *start);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#define INCLUDE_SIMDATA
|
||||
#endif
|
||||
|
||||
#ifdef INCLUDE_SIMDATA
|
||||
extern unsigned const char capture_jpg[];
|
||||
extern unsigned const char octo_jpg[];
|
||||
extern unsigned int octo_jpg_len, capture_jpg_len;
|
||||
#endif
|
|
@ -0,0 +1,193 @@
|
|||
#include "OV2640.h"
|
||||
|
||||
#define TAG "OV2640"
|
||||
|
||||
// definitions appropriate for the ESP32-CAM devboard (and most clones)
|
||||
camera_config_t esp32cam_config{
|
||||
|
||||
.pin_pwdn = -1, // FIXME: on the TTGO T-Journal I think this is GPIO 0
|
||||
.pin_reset = 15,
|
||||
|
||||
.pin_xclk = 27,
|
||||
|
||||
.pin_sscb_sda = 25,
|
||||
.pin_sscb_scl = 23,
|
||||
|
||||
.pin_d7 = 19,
|
||||
.pin_d6 = 36,
|
||||
.pin_d5 = 18,
|
||||
.pin_d4 = 39,
|
||||
.pin_d3 = 5,
|
||||
.pin_d2 = 34,
|
||||
.pin_d1 = 35,
|
||||
.pin_d0 = 17,
|
||||
.pin_vsync = 22,
|
||||
.pin_href = 26,
|
||||
.pin_pclk = 21,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_0,
|
||||
.ledc_channel = LEDC_CHANNEL_0,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
// .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space
|
||||
// .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer
|
||||
// .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12, //0-63 lower numbers are higher quality
|
||||
.fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg
|
||||
};
|
||||
|
||||
camera_config_t esp32cam_aithinker_config{
|
||||
|
||||
.pin_pwdn = 32,
|
||||
.pin_reset = -1,
|
||||
|
||||
.pin_xclk = 0,
|
||||
|
||||
.pin_sscb_sda = 26,
|
||||
.pin_sscb_scl = 27,
|
||||
|
||||
// Note: LED GPIO is apparently 4 not sure where that goes
|
||||
// per https://github.com/donny681/ESP32_CAMERA_QR/blob/e4ef44549876457cd841f33a0892c82a71f35358/main/led.c
|
||||
.pin_d7 = 35,
|
||||
.pin_d6 = 34,
|
||||
.pin_d5 = 39,
|
||||
.pin_d4 = 36,
|
||||
.pin_d3 = 21,
|
||||
.pin_d2 = 19,
|
||||
.pin_d1 = 18,
|
||||
.pin_d0 = 5,
|
||||
.pin_vsync = 25,
|
||||
.pin_href = 23,
|
||||
.pin_pclk = 22,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_1,
|
||||
.ledc_channel = LEDC_CHANNEL_1,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
// .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space
|
||||
// .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer
|
||||
// .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12, //0-63 lower numbers are higher quality
|
||||
.fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg
|
||||
};
|
||||
|
||||
camera_config_t esp32cam_ttgo_t_config{
|
||||
|
||||
.pin_pwdn = 26,
|
||||
.pin_reset = -1,
|
||||
|
||||
.pin_xclk = 32,
|
||||
|
||||
.pin_sscb_sda = 13,
|
||||
.pin_sscb_scl = 12,
|
||||
|
||||
.pin_d7 = 39,
|
||||
.pin_d6 = 36,
|
||||
.pin_d5 = 23,
|
||||
.pin_d4 = 18,
|
||||
.pin_d3 = 15,
|
||||
.pin_d2 = 4,
|
||||
.pin_d1 = 14,
|
||||
.pin_d0 = 5,
|
||||
.pin_vsync = 27,
|
||||
.pin_href = 25,
|
||||
.pin_pclk = 19,
|
||||
.xclk_freq_hz = 20000000,
|
||||
.ledc_timer = LEDC_TIMER_0,
|
||||
.ledc_channel = LEDC_CHANNEL_0,
|
||||
.pixel_format = PIXFORMAT_JPEG,
|
||||
.frame_size = FRAMESIZE_SVGA,
|
||||
.jpeg_quality = 12, //0-63 lower numbers are higher quality
|
||||
.fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg
|
||||
};
|
||||
|
||||
void OV2640::run(void)
|
||||
{
|
||||
if (fb)
|
||||
//return the frame buffer back to the driver for reuse
|
||||
esp_camera_fb_return(fb);
|
||||
|
||||
fb = esp_camera_fb_get();
|
||||
}
|
||||
|
||||
void OV2640::runIfNeeded(void)
|
||||
{
|
||||
if (!fb)
|
||||
run();
|
||||
}
|
||||
|
||||
int OV2640::getWidth(void)
|
||||
{
|
||||
runIfNeeded();
|
||||
return fb->width;
|
||||
}
|
||||
|
||||
int OV2640::getHeight(void)
|
||||
{
|
||||
runIfNeeded();
|
||||
return fb->height;
|
||||
}
|
||||
|
||||
size_t OV2640::getSize(void)
|
||||
{
|
||||
runIfNeeded();
|
||||
if (!fb)
|
||||
return 0; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes?
|
||||
return fb->len;
|
||||
}
|
||||
|
||||
uint8_t *OV2640::getfb(void)
|
||||
{
|
||||
runIfNeeded();
|
||||
if (!fb)
|
||||
return NULL; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes?
|
||||
|
||||
return fb->buf;
|
||||
}
|
||||
|
||||
framesize_t OV2640::getFrameSize(void)
|
||||
{
|
||||
return _cam_config.frame_size;
|
||||
}
|
||||
|
||||
void OV2640::setFrameSize(framesize_t size)
|
||||
{
|
||||
_cam_config.frame_size = size;
|
||||
}
|
||||
|
||||
pixformat_t OV2640::getPixelFormat(void)
|
||||
{
|
||||
return _cam_config.pixel_format;
|
||||
}
|
||||
|
||||
void OV2640::setPixelFormat(pixformat_t format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case PIXFORMAT_RGB565:
|
||||
case PIXFORMAT_YUV422:
|
||||
case PIXFORMAT_GRAYSCALE:
|
||||
case PIXFORMAT_JPEG:
|
||||
_cam_config.pixel_format = format;
|
||||
break;
|
||||
default:
|
||||
_cam_config.pixel_format = PIXFORMAT_GRAYSCALE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t OV2640::init(camera_config_t config)
|
||||
{
|
||||
memset(&_cam_config, 0, sizeof(_cam_config));
|
||||
memcpy(&_cam_config, &config, sizeof(config));
|
||||
|
||||
esp_err_t err = esp_camera_init(&_cam_config);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
printf("Camera probe failed with error 0x%x", err);
|
||||
return err;
|
||||
}
|
||||
// ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef OV2640_H_
|
||||
#define OV2640_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <pgmspace.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_camera.h"
|
||||
|
||||
extern camera_config_t esp32cam_config, esp32cam_aithinker_config, esp32cam_ttgo_t_config;
|
||||
|
||||
class OV2640
|
||||
{
|
||||
public:
|
||||
OV2640(){
|
||||
fb = NULL;
|
||||
};
|
||||
~OV2640(){
|
||||
};
|
||||
esp_err_t init(camera_config_t config);
|
||||
void run(void);
|
||||
size_t getSize(void);
|
||||
uint8_t *getfb(void);
|
||||
int getWidth(void);
|
||||
int getHeight(void);
|
||||
framesize_t getFrameSize(void);
|
||||
pixformat_t getPixelFormat(void);
|
||||
|
||||
void setFrameSize(framesize_t size);
|
||||
void setPixelFormat(pixformat_t format);
|
||||
|
||||
private:
|
||||
void runIfNeeded(); // grab a frame if we don't already have one
|
||||
|
||||
// camera_framesize_t _frame_size;
|
||||
// camera_pixelformat_t _pixel_format;
|
||||
camera_config_t _cam_config;
|
||||
|
||||
camera_fb_t *fb;
|
||||
};
|
||||
|
||||
#endif //OV2640_H_
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
#include "OV2640Streamer.h"
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
|
||||
OV2640Streamer::OV2640Streamer(SOCKET aClient, OV2640 &cam) : CStreamer(aClient, cam.getWidth(), cam.getHeight()), m_cam(cam)
|
||||
{
|
||||
Serial.printf("Created streamer width=%d, height=%d\n", cam.getWidth(), cam.getHeight());
|
||||
}
|
||||
|
||||
void OV2640Streamer::streamImage(uint32_t curMsec)
|
||||
{
|
||||
m_cam.run();// queue up a read for next time
|
||||
|
||||
BufPtr bytes = m_cam.getfb();
|
||||
streamFrame(bytes, m_cam.getSize(), curMsec);
|
||||
Serial.printf("get frame: %d\n",m_cam.getSize());
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "CStreamer.h"
|
||||
#include "OV2640.h"
|
||||
|
||||
class OV2640Streamer : public CStreamer
|
||||
{
|
||||
bool m_showBig;
|
||||
OV2640 &m_cam;
|
||||
|
||||
public:
|
||||
OV2640Streamer(SOCKET aClient, OV2640 &cam);
|
||||
|
||||
virtual void streamImage(uint32_t curMsec);
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
#include "SimStreamer.h"
|
||||
#include "JPEGSamples.h"
|
||||
|
||||
|
||||
#ifdef INCLUDE_SIMDATA
|
||||
SimStreamer::SimStreamer(SOCKET aClient, bool showBig) : CStreamer(aClient, showBig ? 800 : 640, showBig ? 600 : 480)
|
||||
{
|
||||
m_showBig = showBig;
|
||||
}
|
||||
|
||||
void SimStreamer::streamImage(uint32_t curMsec)
|
||||
{
|
||||
if(m_showBig) {
|
||||
BufPtr bytes = capture_jpg;
|
||||
uint32_t len = capture_jpg_len;
|
||||
|
||||
streamFrame(bytes, len, curMsec);
|
||||
}
|
||||
else {
|
||||
BufPtr bytes = octo_jpg;
|
||||
uint32_t len = octo_jpg_len;
|
||||
|
||||
streamFrame(bytes, len, curMsec);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "JPEGSamples.h"
|
||||
#include "CStreamer.h"
|
||||
|
||||
#ifdef INCLUDE_SIMDATA
|
||||
class SimStreamer : public CStreamer
|
||||
{
|
||||
bool m_showBig;
|
||||
public:
|
||||
SimStreamer(SOCKET aClient, bool showBig);
|
||||
|
||||
virtual void streamImage(uint32_t curMsec);
|
||||
};
|
||||
#endif
|
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
//#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
typedef WiFiClient *SOCKET;
|
||||
typedef WiFiUDP *UDPSOCKET;
|
||||
typedef IPAddress IPADDRESS; // On linux use uint32_t in network byte order (per getpeername)
|
||||
typedef uint16_t IPPORT; // on linux use network byte order
|
||||
|
||||
#define NULLSOCKET NULL
|
||||
|
||||
inline void closesocket(SOCKET s) {
|
||||
printf("closing TCP socket\n");
|
||||
|
||||
if(s) {
|
||||
s->stop();
|
||||
// delete s; TDP WiFiClients are never on the heap in arduino land?
|
||||
}
|
||||
}
|
||||
|
||||
#define getRandom() random(65536)
|
||||
|
||||
inline void socketpeeraddr(SOCKET s, IPADDRESS *addr, IPPORT *port) {
|
||||
*addr = s->remoteIP();
|
||||
*port = s->remotePort();
|
||||
}
|
||||
|
||||
inline void udpsocketclose(UDPSOCKET s) {
|
||||
printf("closing UDP socket\n");
|
||||
if(s) {
|
||||
s->stop();
|
||||
delete s;
|
||||
}
|
||||
}
|
||||
|
||||
inline UDPSOCKET udpsocketcreate(unsigned short portNum)
|
||||
{
|
||||
UDPSOCKET s = new WiFiUDP();
|
||||
|
||||
if(!s->begin(portNum)) {
|
||||
printf("Can't bind port %d\n", portNum);
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// TCP sending
|
||||
inline ssize_t socketsend(SOCKET sockfd, const void *buf, size_t len)
|
||||
{
|
||||
return sockfd->write((uint8_t *) buf, len);
|
||||
}
|
||||
|
||||
inline ssize_t udpsocketsend(UDPSOCKET sockfd, const void *buf, size_t len,
|
||||
IPADDRESS destaddr, IPPORT destport)
|
||||
{
|
||||
sockfd->beginPacket(destaddr, destport);
|
||||
sockfd->write((const uint8_t *) buf, len);
|
||||
if(!sockfd->endPacket())
|
||||
printf("error sending udp packet\n");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
Read from a socket with a timeout.
|
||||
|
||||
Return 0=socket was closed by client, -1=timeout, >0 number of bytes read
|
||||
*/
|
||||
inline int socketread(SOCKET sock, char *buf, size_t buflen, int timeoutmsec)
|
||||
{
|
||||
if(!sock->connected()) {
|
||||
printf("client has closed the socket\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int numAvail = sock->available();
|
||||
if(numAvail == 0 && timeoutmsec != 0) {
|
||||
// sleep and hope for more
|
||||
delay(timeoutmsec);
|
||||
numAvail = sock->available();
|
||||
}
|
||||
|
||||
if(numAvail == 0) {
|
||||
// printf("timeout on read\n");
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
// int numRead = sock->readBytesUntil('\n', buf, buflen);
|
||||
int numRead = sock->readBytes(buf, buflen);
|
||||
// printf("bytes avail %d, read %d: %s", numAvail, numRead, buf);
|
||||
return numRead;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
typedef int SOCKET;
|
||||
typedef int UDPSOCKET;
|
||||
typedef uint32_t IPADDRESS; // On linux use uint32_t in network byte order (per getpeername)
|
||||
typedef uint16_t IPPORT; // on linux use network byte order
|
||||
|
||||
#define NULLSOCKET 0
|
||||
|
||||
inline void closesocket(SOCKET s) {
|
||||
close(s);
|
||||
}
|
||||
|
||||
#define getRandom() rand()
|
||||
|
||||
inline void socketpeeraddr(SOCKET s, IPADDRESS *addr, IPPORT *port) {
|
||||
|
||||
sockaddr_in r;
|
||||
socklen_t len = sizeof(r);
|
||||
if(getpeername(s,(struct sockaddr*)&r,&len) < 0) {
|
||||
printf("getpeername failed\n");
|
||||
*addr = 0;
|
||||
*port = 0;
|
||||
}
|
||||
else {
|
||||
//htons
|
||||
|
||||
*port = r.sin_port;
|
||||
*addr = r.sin_addr.s_addr;
|
||||
}
|
||||
}
|
||||
|
||||
inline void udpsocketclose(UDPSOCKET s) {
|
||||
close(s);
|
||||
}
|
||||
|
||||
inline UDPSOCKET udpsocketcreate(unsigned short portNum)
|
||||
{
|
||||
sockaddr_in addr;
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
int s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
addr.sin_port = htons(portNum);
|
||||
if (bind(s,(sockaddr*)&addr,sizeof(addr)) != 0) {
|
||||
printf("Error, can't bind\n");
|
||||
close(s);
|
||||
s = 0;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// TCP sending
|
||||
inline ssize_t socketsend(SOCKET sockfd, const void *buf, size_t len)
|
||||
{
|
||||
// printf("TCP send\n");
|
||||
return send(sockfd, buf, len, 0);
|
||||
}
|
||||
|
||||
inline ssize_t udpsocketsend(UDPSOCKET sockfd, const void *buf, size_t len,
|
||||
IPADDRESS destaddr, uint16_t destport)
|
||||
{
|
||||
sockaddr_in addr;
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = destaddr;
|
||||
addr.sin_port = htons(destport);
|
||||
//printf("UDP send to 0x%0x:%0x\n", destaddr, destport);
|
||||
|
||||
return sendto(sockfd, buf, len, 0, (sockaddr *) &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
Read from a socket with a timeout.
|
||||
|
||||
Return 0=socket was closed by client, -1=timeout, >0 number of bytes read
|
||||
*/
|
||||
inline int socketread(SOCKET sock, char *buf, size_t buflen, int timeoutmsec)
|
||||
{
|
||||
// Use a timeout on our socket read to instead serve frames
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = timeoutmsec * 1000; // send a new frame ever
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||
|
||||
int res = recv(sock,buf,buflen,0);
|
||||
if(res > 0) {
|
||||
return res;
|
||||
}
|
||||
else if(res == 0) {
|
||||
return 0; // client dropped connection
|
||||
}
|
||||
else {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||||
return -1;
|
||||
else
|
||||
return 0; // unknown error, just claim client dropped it
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "platglue-esp32.h"
|
||||
#else
|
||||
#include "platglue-posix.h"
|
||||
#endif
|
Loading…
Reference in New Issue