├── .gitignore ├── examples ├── .gitignore ├── wifikeys_template.h ├── platformio.ini └── ESP32-devcam.ino ├── test ├── runvlc.sh ├── devvlc.sh ├── Makefile ├── README.md ├── RTSPTestServer.cpp └── rfccode.cpp ├── src ├── platglue.h ├── JPEGSamples.h ├── OV2640Streamer.h ├── SimStreamer.h ├── OV2640Streamer.cpp ├── SimStreamer.cpp ├── OV2640.h ├── LinkedListElement.h ├── platglue-mbed.h ├── platglue-esp32.h ├── CStreamer.h ├── platglue-posix.h ├── CRtspSession.h ├── OV2640.cpp ├── CStreamer.cpp └── CRtspSession.cpp ├── library.properties ├── library.json ├── TODO.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | testserver 2 | octo.jpg 3 | *.exe 4 | *.o 5 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | wifikeys.h 2 | .pioenvs 3 | .piolibdeps 4 | -------------------------------------------------------------------------------- /test/runvlc.sh: -------------------------------------------------------------------------------- 1 | # for testing 2 | vlc -v rtsp://127.0.0.1:8554/mjpeg/1 3 | -------------------------------------------------------------------------------- /test/devvlc.sh: -------------------------------------------------------------------------------- 1 | # for testing 2 | vlc -v rtsp://192.168.86.215:8554/mjpeg/1 3 | -------------------------------------------------------------------------------- /src/platglue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ARDUINO_ARCH_ESP32 4 | #include "platglue-esp32.h" 5 | #else 6 | #include "platglue-posix.h" 7 | #endif 8 | -------------------------------------------------------------------------------- /examples/wifikeys_template.h: -------------------------------------------------------------------------------- 1 | // copy this file to wifikeys.h and edit 2 | const char *ssid = "YOURNETHERE"; // Put your SSID here 3 | const char *password = "YOURPASSWORDHERE"; // Put your PASSWORD here 4 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | 2 | SRCS = ../src/CRtspSession.cpp ../src/CStreamer.cpp ../src/JPEGSamples.cpp ../src/SimStreamer.cpp 3 | 4 | run: *.cpp ../src/* 5 | #skill testerver 6 | g++ -Wall -o testserver -I ../src -I . *.cpp $(SRCS) 7 | #./testserver 8 | -------------------------------------------------------------------------------- /src/JPEGSamples.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef ARDUINO_ARCH_ESP32 4 | #define INCLUDE_SIMDATA 5 | #endif 6 | 7 | #ifdef INCLUDE_SIMDATA 8 | extern unsigned const char capture_jpg[]; 9 | extern unsigned const char octo_jpg[]; 10 | extern unsigned int octo_jpg_len, capture_jpg_len; 11 | #endif 12 | -------------------------------------------------------------------------------- /src/OV2640Streamer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CStreamer.h" 4 | #include "OV2640.h" 5 | 6 | class OV2640Streamer : public CStreamer 7 | { 8 | bool m_showBig; 9 | OV2640 *m_cam; 10 | 11 | public: 12 | OV2640Streamer(OV2640 *cam); 13 | 14 | virtual void streamImage(uint32_t curMsec); 15 | }; 16 | -------------------------------------------------------------------------------- /src/SimStreamer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JPEGSamples.h" 4 | #include "CStreamer.h" 5 | 6 | #ifdef INCLUDE_SIMDATA 7 | class SimStreamer : public CStreamer 8 | { 9 | bool m_showBig; 10 | public: 11 | SimStreamer(bool showBig); 12 | 13 | virtual void streamImage(uint32_t curMsec); 14 | }; 15 | #endif 16 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Micro-RTSP 2 | version=0.1.6 3 | author=Kevin Hester 4 | maintainer=Kevin Hester 5 | sentence=Mikro RTSP server for mikros 6 | paragraph=A small/efficient RTSP server for ESP32 and other micros 7 | category=Data Storage 8 | url=https://github.com/geeksville/Micro-RTSP.git 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/OV2640Streamer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "OV2640Streamer.h" 3 | #include 4 | 5 | 6 | 7 | OV2640Streamer::OV2640Streamer(OV2640 *cam) : CStreamer(cam->getWidth(), cam->getHeight()), m_cam(cam) 8 | { 9 | printf("Created streamer width=%d, height=%d\n", cam->getWidth(), cam->getHeight()); 10 | } 11 | 12 | void OV2640Streamer::streamImage(uint32_t curMsec) 13 | { 14 | m_cam->run();// queue up a read for next time 15 | 16 | BufPtr bytes = m_cam->getfb(); 17 | streamFrame(bytes, m_cam->getSize(), curMsec); 18 | m_cam->done(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:m5stack-core-esp32] 12 | platform = espressif32@>=1.6.0 13 | board = m5stack-core-esp32 14 | framework = arduino 15 | lib_deps = Micro-RTSP 16 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Micro-RTSP", 3 | "keywords": "esp32, camera, esp32-cam, rtsp", 4 | "description": "A small/efficient RTSP server for ESP32 and other micros", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/geeksville/Micro-RTSP.git" 9 | }, 10 | "authors": 11 | [ 12 | { 13 | "name": "Kevin Hester", 14 | "email": "kevinh@geeksville.com", 15 | "url": "https://github.com/geeksville", 16 | "maintainer": true 17 | } 18 | ], 19 | "version": "0.1.6", 20 | "frameworks": "arduino", 21 | "platforms": "*" 22 | } 23 | -------------------------------------------------------------------------------- /src/SimStreamer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "SimStreamer.h" 3 | #include "JPEGSamples.h" 4 | 5 | 6 | #ifdef INCLUDE_SIMDATA 7 | SimStreamer::SimStreamer(bool showBig) : CStreamer(showBig ? 800 : 640, showBig ? 600 : 480) 8 | { 9 | m_showBig = showBig; 10 | } 11 | 12 | void SimStreamer::streamImage(uint32_t curMsec) 13 | { 14 | if(m_showBig) { 15 | BufPtr bytes = capture_jpg; 16 | uint32_t len = capture_jpg_len; 17 | 18 | streamFrame(bytes, len, curMsec); 19 | } 20 | else { 21 | BufPtr bytes = octo_jpg; 22 | uint32_t len = octo_jpg_len; 23 | 24 | streamFrame(bytes, len, curMsec); 25 | } 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testserver 2 | 3 | This is a standalone Linux/cygwin test application to allow development of this 4 | library without going through the slow process of always testing on the ESP32. 5 | Almost all of the code is the same - only platglue-posix.h differs from 6 | platglue-esp32.h (thus serving as a crude HAL). 7 | 8 | RESPTestServer.cpp also serves as a small example of how this library could 9 | be used on Poxix systems. 10 | 11 | # Usage 12 | 13 | Run "make" to build and run the server. Run "runvlc.sh" to fire up a VLC client 14 | that talks to that server. If all is working you should see a static image 15 | of my office that I captured using a ESP32-CAM. 16 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * add instructions for example app 2 | * push RTSP streams to other servers ( https://github.com/ant-media/Ant-Media-Server/wiki/Getting-Started ) 3 | * make stack larger so that the various scratch buffers (currently in bss) can be shared 4 | * cleanup code to a less ugly unified coding standard 5 | * support multiple simultaneous clients on the device 6 | * make octocat test image work again (by changing encoding type from 1 to 0 (422 vs 420)) 7 | 8 | DONE: 9 | * serve real jpegs (use correct quantization & huffman tables) 10 | * test that both TCP and UDP clients work 11 | * change framerate to something slow 12 | * test remote access 13 | * select a licence and put license into github 14 | * find cause of new mystery pause when starting up in sim mode 15 | * split sim code from real code via inheritence 16 | * use device camera 17 | * package the ESP32-CAM stuff as a library so I can depend on it 18 | * package as a library https://docs.platformio.org/en/latest/librarymanager/creating.html#library-creating-examples 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 S. Kevin Hester-Chow, kevinh@geeksville.com (MIT License) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/OV2640.h: -------------------------------------------------------------------------------- 1 | #ifndef OV2640_H_ 2 | #define OV2640_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "esp_log.h" 8 | #include "esp_attr.h" 9 | #include "esp_camera.h" 10 | 11 | extern camera_config_t esp32cam_config, esp32cam_aithinker_config, esp32cam_ttgo_t_config; 12 | 13 | class OV2640 14 | { 15 | public: 16 | OV2640(){ 17 | fb = NULL; 18 | }; 19 | ~OV2640(){ 20 | }; 21 | esp_err_t init(camera_config_t config); 22 | void done(void); 23 | void run(void); 24 | size_t getSize(void); 25 | uint8_t *getfb(void); 26 | int getWidth(void); 27 | int getHeight(void); 28 | framesize_t getFrameSize(void); 29 | pixformat_t getPixelFormat(void); 30 | 31 | void setFrameSize(framesize_t size); 32 | void setPixelFormat(pixformat_t format); 33 | 34 | private: 35 | void runIfNeeded(); // grab a frame if we don't already have one 36 | 37 | // camera_framesize_t _frame_size; 38 | // camera_pixelformat_t _pixel_format; 39 | camera_config_t _cam_config; 40 | 41 | camera_fb_t *fb; 42 | }; 43 | 44 | #endif //OV2640_H_ 45 | -------------------------------------------------------------------------------- /src/LinkedListElement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platglue.h" 4 | #include 5 | 6 | class LinkedListElement 7 | { 8 | public: 9 | LinkedListElement* m_Next; 10 | LinkedListElement* m_Prev; 11 | 12 | LinkedListElement(void) 13 | { 14 | m_Next = this; 15 | m_Prev = this; 16 | printf("LinkedListElement (%p)->(%p)->(%p)\n", m_Prev, this, m_Next); 17 | } 18 | 19 | int NotEmpty(void) 20 | { 21 | return (m_Next != this); 22 | } 23 | 24 | LinkedListElement(LinkedListElement* linkedList) 25 | { 26 | // add to the end of list 27 | m_Prev = linkedList->m_Prev; 28 | linkedList->m_Prev = this; 29 | m_Prev->m_Next = this; 30 | m_Next = linkedList; 31 | printf("LinkedListElement (%p)->(%p)->(%p)\n", m_Prev, this, m_Next); 32 | } 33 | 34 | ~LinkedListElement() 35 | { 36 | printf("~LinkedListElement(%p)->(%p)->(%p)\n", m_Prev, this, m_Next); 37 | if (m_Next) 38 | m_Next->m_Prev = m_Prev; 39 | if (m_Prev) 40 | m_Prev->m_Next = m_Next; 41 | printf("~LinkedListElement after: (%p)->(%p)", m_Prev, m_Prev->m_Next); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/platglue-mbed.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Marco Garzola 3 | */ 4 | #pragma once 5 | 6 | #include "mbed.h" 7 | 8 | typedef TCPSocket* SOCKET; 9 | typedef UDPSocket* UDPSOCKET; 10 | typedef SocketAddress IPADDRESS; 11 | typedef uint16_t IPPORT; 12 | 13 | #define SEND_TMEOUT_MS 1000 14 | #define NULLSOCKET NULL 15 | 16 | inline void closesocket(SOCKET s) 17 | { 18 | if (s) 19 | { 20 | s->close(); 21 | } 22 | } 23 | 24 | #define getRandom() rand() 25 | 26 | inline void socketpeeraddr(SOCKET s, IPADDRESS* addr, IPPORT* port) 27 | { 28 | s->getpeername(addr); 29 | *port = addr->get_port(); 30 | } 31 | 32 | inline UDPSOCKET udpsocketcreate(unsigned short portNum) 33 | { 34 | UDPSOCKET s = new UDPSocket(); 35 | 36 | if (s->open(NetworkInterface::get_default_instance()) != 0 && s->bind(portNum) != 0) 37 | { 38 | printf("Can't bind port %d\n", portNum); 39 | delete s; 40 | return nullptr; 41 | } 42 | return s; 43 | } 44 | 45 | inline void udpsocketclose(UDPSOCKET s) 46 | { 47 | if (s) 48 | { 49 | s->close(); 50 | delete s; 51 | } 52 | } 53 | 54 | inline ssize_t 55 | udpsocketsend(UDPSOCKET sockfd, const void* buf, size_t len, IPADDRESS destaddr, uint16_t destport) 56 | { 57 | if (sockfd) 58 | { 59 | return sockfd->sendto(destaddr.get_ip_address(), destport, buf, len); 60 | } 61 | else 62 | { 63 | return 0; 64 | } 65 | } 66 | // TCP sending 67 | inline ssize_t socketsend(SOCKET sockfd, const void* buf, size_t len) 68 | { 69 | if (sockfd && buf) 70 | { 71 | sockfd->set_blocking(true); 72 | sockfd->set_timeout(SEND_TMEOUT_MS); 73 | return sockfd->send(buf, len); 74 | } 75 | else 76 | { 77 | return 0; 78 | } 79 | } 80 | 81 | inline int socketread(SOCKET sock, char* buf, size_t buflen, int timeoutmsec) 82 | { 83 | if (sock && buf) 84 | { 85 | sock->set_blocking(true); 86 | sock->set_timeout(timeoutmsec); 87 | return sock->recv(buf, buflen); 88 | } 89 | else 90 | { 91 | return -1; 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /test/RTSPTestServer.cpp: -------------------------------------------------------------------------------- 1 | #include "platglue.h" 2 | 3 | #include "SimStreamer.h" 4 | #include "CRtspSession.h" 5 | #include "JPEGSamples.h" 6 | #include 7 | #include 8 | 9 | 10 | void workerThread( SOCKET s ) 11 | { 12 | SimStreamer streamer( true ); // our streamer for UDP/TCP based RTP transport. true == use bigger resolution 13 | 14 | streamer.addSession( s )->debug = true; // our threads RTSP session and state 15 | 16 | while ( streamer.anySessions() ) 17 | { 18 | uint32_t timeout = 400; 19 | if( ! streamer.handleRequests( timeout ) ) 20 | { 21 | struct timeval now; 22 | gettimeofday( &now, NULL ); // crufty msecish timer 23 | uint32_t msec = now.tv_sec * 1000 + now.tv_usec / 1000; 24 | streamer.streamImage( msec ); 25 | } 26 | } 27 | } 28 | 29 | int main() 30 | { 31 | SOCKET MasterSocket; // our masterSocket(socket that listens for RTSP client connections) 32 | SOCKET ClientSocket; // RTSP socket to handle an client 33 | sockaddr_in ServerAddr; // server address parameters 34 | sockaddr_in ClientAddr; // address parameters of a new RTSP client 35 | socklen_t ClientAddrLen = sizeof(ClientAddr); 36 | 37 | printf( "running test RTSP server\n" ); 38 | 39 | ServerAddr.sin_family = AF_INET; 40 | ServerAddr.sin_addr.s_addr = INADDR_ANY; 41 | ServerAddr.sin_port = htons(8554); // listen on RTSP port 8554 42 | MasterSocket = socket(AF_INET,SOCK_STREAM,0); 43 | 44 | int enable = 1; 45 | if (setsockopt(MasterSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { 46 | printf("setsockopt(SO_REUSEADDR) failed"); 47 | return 0; 48 | } 49 | 50 | // bind our master socket to the RTSP port and listen for a client connection 51 | if (bind(MasterSocket,(sockaddr*)&ServerAddr,sizeof(ServerAddr)) != 0) { 52 | printf("error can't bind port errno=%d\n", errno); 53 | 54 | return 0; 55 | } 56 | 57 | if (listen(MasterSocket,5) != 0) return 0; 58 | 59 | while ( true ) 60 | { // loop forever to accept client connections 61 | ClientSocket = accept(MasterSocket,(struct sockaddr*)&ClientAddr,&ClientAddrLen); 62 | printf("Client connected. Client address: %s\r\n",inet_ntoa(ClientAddr.sin_addr)); 63 | if(fork() == 0) 64 | workerThread(ClientSocket); 65 | } 66 | 67 | closesocket(MasterSocket); 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /src/platglue-esp32.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | //#include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | typedef WiFiClient *SOCKET; 18 | typedef WiFiUDP *UDPSOCKET; 19 | typedef IPAddress IPADDRESS; // On linux use uint32_t in network byte order (per getpeername) 20 | typedef uint16_t IPPORT; // on linux use network byte order 21 | 22 | #define NULLSOCKET NULL 23 | 24 | inline void closesocket(SOCKET s) { 25 | printf("closing TCP socket\n"); 26 | 27 | if(s) { 28 | s->stop(); 29 | // delete s; TDP WiFiClients are never on the heap in arduino land? 30 | } 31 | } 32 | 33 | #define getRandom() random(65536) 34 | 35 | inline void socketpeeraddr(SOCKET s, IPADDRESS *addr, IPPORT *port) { 36 | *addr = s->remoteIP(); 37 | *port = s->remotePort(); 38 | } 39 | 40 | inline void udpsocketclose(UDPSOCKET s) { 41 | printf("closing UDP socket\n"); 42 | if(s) { 43 | s->stop(); 44 | delete s; 45 | } 46 | } 47 | 48 | inline UDPSOCKET udpsocketcreate(unsigned short portNum) 49 | { 50 | UDPSOCKET s = new WiFiUDP(); 51 | 52 | if(!s->begin(portNum)) { 53 | printf("Can't bind port %d\n", portNum); 54 | delete s; 55 | return NULL; 56 | } 57 | 58 | return s; 59 | } 60 | 61 | // TCP sending 62 | inline ssize_t socketsend(SOCKET sockfd, const void *buf, size_t len) 63 | { 64 | return sockfd->write((uint8_t *) buf, len); 65 | } 66 | 67 | inline ssize_t udpsocketsend(UDPSOCKET sockfd, const void *buf, size_t len, 68 | IPADDRESS destaddr, IPPORT destport) 69 | { 70 | sockfd->beginPacket(destaddr, destport); 71 | sockfd->write((const uint8_t *) buf, len); 72 | if(!sockfd->endPacket()) 73 | printf("error sending udp packet\n"); 74 | 75 | return len; 76 | } 77 | 78 | /** 79 | Read from a socket with a timeout. 80 | 81 | Return 0=socket was closed by client, -1=timeout, >0 number of bytes read 82 | */ 83 | inline int socketread(SOCKET sock, char *buf, size_t buflen, int timeoutmsec) 84 | { 85 | if(!sock->connected()) { 86 | printf("client has closed the socket\n"); 87 | return 0; 88 | } 89 | 90 | int numAvail = sock->available(); 91 | if(numAvail == 0 && timeoutmsec != 0) { 92 | // sleep and hope for more 93 | delay(timeoutmsec); 94 | numAvail = sock->available(); 95 | } 96 | 97 | if(numAvail == 0) { 98 | // printf("timeout on read\n"); 99 | return -1; 100 | } 101 | else { 102 | // int numRead = sock->readBytesUntil('\n', buf, buflen); 103 | int numRead = sock->readBytes(buf, buflen); 104 | // printf("bytes avail %d, read %d: %s", numAvail, numRead, buf); 105 | return numRead; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/CStreamer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platglue.h" 4 | #include "LinkedListElement.h" 5 | 6 | typedef unsigned const char *BufPtr; 7 | 8 | class CRtspSession; 9 | 10 | class CStreamer 11 | { 12 | public: 13 | CStreamer( u_short width, u_short height ); 14 | virtual ~CStreamer(); 15 | 16 | CRtspSession *addSession( SOCKET aClient ); 17 | LinkedListElement* getClientsListHead() { return &m_Clients; } 18 | 19 | int anySessions() { return m_Clients.NotEmpty(); } 20 | 21 | bool handleRequests(uint32_t readTimeoutMs); 22 | 23 | u_short GetRtpServerPort(); 24 | u_short GetRtcpServerPort(); 25 | 26 | virtual void streamImage(uint32_t curMsec) = 0; // send a new image to the client 27 | bool InitUdpTransport(void); 28 | void ReleaseUdpTransport(void); 29 | bool debug; 30 | void setURI( String hostport, String pres = "mjpeg", String stream = "1" ); // set URI parts for sessions to use. 31 | String getURIHost(){ return m_URIHost; }; // for getting things back by sessions 32 | String getURIPresentation(){ return m_URIPresentation; }; 33 | String getURIStream(){ return m_URIStream; }; 34 | 35 | protected: 36 | 37 | void streamFrame(unsigned const char *data, uint32_t dataLen, uint32_t curMsec); 38 | 39 | String m_URIHost; // Host:port URI part that client should use to connect. also it is reported in session answers where appropriate. 40 | String m_URIPresentation; // name of presentation part of URI. sessions will check if client used correct one 41 | String m_URIStream; // stream part of the URI. 42 | 43 | private: 44 | 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 45 | 46 | UDPSOCKET m_RtpSocket; // RTP socket for streaming RTP packets to client 47 | UDPSOCKET m_RtcpSocket; // RTCP socket for sending/receiving RTCP packages 48 | 49 | IPPORT m_RtpServerPort; // RTP sender port on server 50 | IPPORT m_RtcpServerPort; // RTCP sender port on server 51 | 52 | u_short m_SequenceNumber; 53 | uint32_t m_Timestamp; 54 | int m_SendIdx; 55 | 56 | LinkedListElement m_Clients; 57 | uint32_t m_prevMsec; 58 | 59 | int m_udpRefCount; 60 | 61 | u_short m_width; // image data info 62 | u_short m_height; 63 | }; 64 | 65 | 66 | 67 | // When JPEG is stored as a file it is wrapped in a container 68 | // This function fixes up the provided start ptr to point to the 69 | // actual JPEG stream data and returns the number of bytes skipped 70 | // returns true if the file seems to be valid jpeg 71 | // If quant tables can be found they will be stored in qtable0/1 72 | bool decodeJPEGfile(BufPtr *start, uint32_t *len, BufPtr *qtable0, BufPtr *qtable1); 73 | bool findJPEGheader(BufPtr *start, uint32_t *len, uint8_t marker); 74 | 75 | // Given a jpeg ptr pointing to a pair of length bytes, advance the pointer to 76 | // the next 0xff marker byte 77 | void nextJpegBlock(BufPtr *start); 78 | -------------------------------------------------------------------------------- /src/platglue-posix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | typedef std::string String; 15 | 16 | typedef int SOCKET; 17 | typedef int UDPSOCKET; 18 | typedef uint32_t IPADDRESS; // On linux use uint32_t in network byte order (per getpeername) 19 | typedef uint16_t IPPORT; // on linux use network byte order 20 | 21 | #define NULLSOCKET 0 22 | 23 | inline void closesocket(SOCKET s) { 24 | close(s); 25 | } 26 | 27 | #define getRandom() rand() 28 | 29 | inline void socketpeeraddr(SOCKET s, IPADDRESS *addr, IPPORT *port) { 30 | 31 | sockaddr_in r; 32 | socklen_t len = sizeof(r); 33 | if(getpeername(s,(struct sockaddr*)&r,&len) < 0) { 34 | printf("getpeername failed\n"); 35 | *addr = 0; 36 | *port = 0; 37 | } 38 | else { 39 | //htons 40 | 41 | *port = r.sin_port; 42 | *addr = r.sin_addr.s_addr; 43 | } 44 | } 45 | 46 | inline void udpsocketclose(UDPSOCKET s) { 47 | close(s); 48 | } 49 | 50 | inline UDPSOCKET udpsocketcreate(unsigned short portNum) 51 | { 52 | sockaddr_in addr; 53 | 54 | addr.sin_family = AF_INET; 55 | addr.sin_addr.s_addr = INADDR_ANY; 56 | 57 | int s = socket(AF_INET, SOCK_DGRAM, 0); 58 | addr.sin_port = htons(portNum); 59 | if (bind(s,(sockaddr*)&addr,sizeof(addr)) != 0) { 60 | printf("Error, can't bind\n"); 61 | close(s); 62 | s = 0; 63 | } 64 | 65 | return s; 66 | } 67 | 68 | // TCP sending 69 | inline ssize_t socketsend(SOCKET sockfd, const void *buf, size_t len) 70 | { 71 | // printf("TCP send\n"); 72 | return send(sockfd, buf, len, 0); 73 | } 74 | 75 | inline ssize_t udpsocketsend(UDPSOCKET sockfd, const void *buf, size_t len, 76 | IPADDRESS destaddr, uint16_t destport) 77 | { 78 | sockaddr_in addr; 79 | 80 | addr.sin_family = AF_INET; 81 | addr.sin_addr.s_addr = destaddr; 82 | addr.sin_port = htons(destport); 83 | //printf("UDP send to 0x%0x:%0x\n", destaddr, destport); 84 | 85 | return sendto(sockfd, buf, len, 0, (sockaddr *) &addr, sizeof(addr)); 86 | } 87 | 88 | /** 89 | Read from a socket with a timeout. 90 | 91 | Return 0=socket was closed by client, -1=timeout, >0 number of bytes read 92 | */ 93 | inline int socketread(SOCKET sock, char *buf, size_t buflen, int timeoutmsec) 94 | { 95 | // Use a timeout on our socket read to instead serve frames 96 | struct timeval tv; 97 | tv.tv_sec = 0; 98 | tv.tv_usec = timeoutmsec * 1000; // send a new frame ever 99 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); 100 | 101 | int res = recv(sock,buf,buflen,0); 102 | if(res > 0) { 103 | return res; 104 | } 105 | else if(res == 0) { 106 | return 0; // client dropped connection 107 | } 108 | else { 109 | if (errno == EWOULDBLOCK || errno == EAGAIN) 110 | return -1; 111 | else 112 | return 0; // unknown error, just claim client dropped it 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /src/CRtspSession.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LinkedListElement.h" 4 | #include "CStreamer.h" 5 | #include "platglue.h" 6 | 7 | // supported command types 8 | enum RTSP_CMD_TYPES 9 | { 10 | RTSP_OPTIONS, 11 | RTSP_DESCRIBE, 12 | RTSP_SETUP, 13 | RTSP_PLAY, 14 | RTSP_TEARDOWN, 15 | RTSP_UNKNOWN 16 | }; 17 | 18 | #define RTSP_BUFFER_SIZE 10000 // for incoming requests, and outgoing responses 19 | #define RTSP_PARAM_STRING_MAX 200 20 | #define MAX_HOSTNAME_LEN 256 21 | 22 | class CRtspSession : public LinkedListElement 23 | { 24 | public: 25 | CRtspSession( SOCKET aRtspClient, CStreamer * aStreamer ); 26 | ~CRtspSession(); 27 | 28 | RTSP_CMD_TYPES Handle_RtspRequest( char *aRequest, unsigned aRequestSize ); 29 | int GetStreamID(); 30 | 31 | /** 32 | Read from our socket, parsing commands as possible. 33 | 34 | return false if the read timed out 35 | */ 36 | bool handleRequests(uint32_t readTimeoutMs); 37 | 38 | bool m_streaming; 39 | bool m_stopped; 40 | 41 | void InitTransport(u_short aRtpPort, u_short aRtcpPort); 42 | 43 | bool isTcpTransport() { return m_TcpTransport; } 44 | SOCKET& getClient() { return m_RtspClient; } 45 | 46 | uint16_t getRtpClientPort() { return m_RtpClientPort; } 47 | 48 | bool debug; /// set to true to get a load of output 49 | private: 50 | void newCommandInit(); 51 | bool ParseRtspRequest( char * aRequest, unsigned aRequestSize ); 52 | char const * DateHeader(); 53 | 54 | // RTSP request command handlers 55 | void Handle_RtspOPTION(); 56 | void Handle_RtspDESCRIBE(); 57 | void Handle_RtspSETUP(); 58 | void Handle_RtspPLAY(); 59 | 60 | // global session state parameters 61 | int m_RtspSessionID; 62 | SOCKET m_Client; 63 | SOCKET m_RtspClient; /// RTSP socket of that session 64 | int m_StreamID; /// number of simulated stream of that session 65 | IPPORT m_ClientRTPPort; /// client port for UDP based RTP transport 66 | IPPORT m_ClientRTCPPort; /// client port for UDP based RTCP transport 67 | bool m_TcpTransport; /// if Tcp based streaming was activated 68 | CStreamer * m_Streamer; /// the UDP or TCP streamer of that session 69 | 70 | // parameters of the last received RTSP request 71 | RTSP_CMD_TYPES m_RtspCmdType; /// command type (if any) of the current request 72 | char m_CommandPresentationPart[RTSP_PARAM_STRING_MAX]; /// stream name pre suffix 73 | char m_CommandStreamPart[RTSP_PARAM_STRING_MAX]; /// stream name suffix 74 | char m_CommandHostPort[MAX_HOSTNAME_LEN]; /// host:port part of the URL 75 | unsigned m_CSeq; /// RTSP command sequence number 76 | unsigned m_ContentLength; /// SDP string size 77 | 78 | uint16_t m_RtpClientPort; // RTP receiver port on client (in host byte order!) 79 | uint16_t m_RtcpClientPort; // RTCP receiver port on client (in host byte order!) 80 | }; 81 | -------------------------------------------------------------------------------- /test/rfccode.cpp: -------------------------------------------------------------------------------- 1 | #include "platglue.h" 2 | 3 | #include "SimStreamer.h" 4 | #include "CRtspSession.h" 5 | #include "JPEGSamples.h" 6 | 7 | 8 | // From RFC2435 generates standard quantization tables 9 | 10 | /* 11 | * Table K.1 from JPEG spec. 12 | */ 13 | static const int jpeg_luma_quantizer[64] = { 14 | 16, 11, 10, 16, 24, 40, 51, 61, 15 | 12, 12, 14, 19, 26, 58, 60, 55, 16 | 14, 13, 16, 24, 40, 57, 69, 56, 17 | 14, 17, 22, 29, 51, 87, 80, 62, 18 | 18, 22, 37, 56, 68, 109, 103, 77, 19 | 24, 35, 55, 64, 81, 104, 113, 92, 20 | 49, 64, 78, 87, 103, 121, 120, 101, 21 | 72, 92, 95, 98, 112, 100, 103, 99 22 | }; 23 | 24 | /* 25 | * Table K.2 from JPEG spec. 26 | */ 27 | static const int jpeg_chroma_quantizer[64] = { 28 | 17, 18, 24, 47, 99, 99, 99, 99, 29 | 18, 21, 26, 66, 99, 99, 99, 99, 30 | 24, 26, 56, 99, 99, 99, 99, 99, 31 | 47, 66, 99, 99, 99, 99, 99, 99, 32 | 99, 99, 99, 99, 99, 99, 99, 99, 33 | 99, 99, 99, 99, 99, 99, 99, 99, 34 | 99, 99, 99, 99, 99, 99, 99, 99, 35 | 99, 99, 99, 99, 99, 99, 99, 99 36 | }; 37 | 38 | /* 39 | * Call MakeTables with the Q factor and two u_char[64] return arrays 40 | */ 41 | void 42 | MakeTables(int q, u_char *lqt, u_char *cqt) 43 | { 44 | int i; 45 | int factor = q; 46 | 47 | if (q < 1) factor = 1; 48 | if (q > 99) factor = 99; 49 | if (q < 50) 50 | q = 5000 / factor; 51 | else 52 | q = 200 - factor*2; 53 | 54 | for (i=0; i < 64; i++) { 55 | int lq = (jpeg_luma_quantizer[i] * q + 50) / 100; 56 | int cq = (jpeg_chroma_quantizer[i] * q + 50) / 100; 57 | 58 | /* Limit the quantizers to 1 <= q <= 255 */ 59 | if (lq < 1) lq = 1; 60 | else if (lq > 255) lq = 255; 61 | lqt[i] = lq; 62 | 63 | if (cq < 1) cq = 1; 64 | else if (cq > 255) cq = 255; 65 | cqt[i] = cq; 66 | } 67 | } 68 | 69 | 70 | 71 | // analyze an imge from our camera to find which quant table it is using... 72 | // Used to see if our camera is spitting out standard RTP tables (it isn't) 73 | 74 | // So we have to use Q of 255 to indicate that each frame has unique quant tables 75 | // use 0 for precision in the qant header, 64 for length 76 | void findCameraQuant() 77 | { 78 | BufPtr bytes = capture_jpg; 79 | uint32_t len = capture_jpg_len; 80 | 81 | if(!findJPEGheader(&bytes, &len, 0xdb)) { 82 | printf("error can't find quant table 0\n"); 83 | return; 84 | } 85 | else { 86 | printf("found quant table %x (len %d)\n", bytes[2], bytes[1]); 87 | } 88 | BufPtr qtable0 = bytes + 3; // 3 bytes of header skipped 89 | 90 | nextJpegBlock(&bytes); 91 | if(!findJPEGheader(&bytes, &len, 0xdb)) { 92 | printf("error can't find quant table 1\n"); 93 | return; 94 | } 95 | else { 96 | printf("found quant table %x\n", bytes[2]); 97 | } 98 | BufPtr qtable1 = bytes + 3; 99 | 100 | nextJpegBlock(&bytes); 101 | 102 | for(int q = 0; q < 128; q++) { 103 | uint8_t lqt[64], cqt[64]; 104 | MakeTables(q, lqt, cqt); 105 | 106 | if(memcmp(qtable0, lqt, sizeof(lqt)) == 0 && memcmp(qtable1, cqt, sizeof(cqt)) == 0) { 107 | printf("Found matching quant table %d\n", q); 108 | } 109 | } 110 | printf("No matching quant table found!\n"); 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro-RTSP 2 | 3 | This is a small library which can be used to serve up RTSP streams from 4 | resource constrained MCUs. It lets you trivially make a $10 open source 5 | RTSP video stream camera. 6 | 7 | # Usage 8 | 9 | This library works for ESP32/arduino targets but also for most any posixish platform. 10 | 11 | ## Example arduino/ESP32 usage 12 | 13 | This library will work standalone, but it is _super_ easy to use if your app is platform.io based. 14 | Just "pio lib install Micro-RTSP" to pull the latest version from their library server. If you want to use the OV2640 15 | camera support you'll need to be targeting the espressif32 platform in your project. 16 | 17 | See the [example platform.io app](/examples). It should build and run on virtually any of the $10 18 | ESP32-CAM boards (such as M5CAM). The relevant bit of the code is included below. In short: 19 | 1. Listen for a TCP connection on the RTSP port with accept() 20 | 2. When a connection comes in, create a CRtspSession and OV2640Streamer camera streamer objects. 21 | 3. While the connection remains, call session->handleRequests(0) to handle any incoming client requests. 22 | 4. Every 100ms or so call session->broadcastCurrentFrame() to send new frames to any clients. 23 | 24 | ``` 25 | void loop() 26 | { 27 | uint32_t msecPerFrame = 100; 28 | static uint32_t lastimage = millis(); 29 | 30 | // If we have an active client connection, just service that until gone 31 | // (FIXME - support multiple simultaneous clients) 32 | if(session) { 33 | session->handleRequests(0); // we don't use a timeout here, 34 | // instead we send only if we have new enough frames 35 | 36 | uint32_t now = millis(); 37 | if(now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover 38 | session->broadcastCurrentFrame(now); 39 | lastimage = now; 40 | 41 | // check if we are overrunning our max frame rate 42 | now = millis(); 43 | if(now > lastimage + msecPerFrame) 44 | printf("warning exceeding max frame rate of %d ms\n", now - lastimage); 45 | } 46 | 47 | if(session->m_stopped) { 48 | delete session; 49 | delete streamer; 50 | session = NULL; 51 | streamer = NULL; 52 | } 53 | } 54 | else { 55 | client = rtspServer.accept(); 56 | 57 | if(client) { 58 | //streamer = new SimStreamer(&client, true); // our streamer for UDP/TCP based RTP transport 59 | streamer = new OV2640Streamer(&client, cam); // our streamer for UDP/TCP based RTP transport 60 | 61 | session = new CRtspSession(&client, streamer); // our threads RTSP session and state 62 | } 63 | } 64 | } 65 | ``` 66 | ## Example posix/linux usage 67 | 68 | There is a small standalone example [here](/test/RTSPTestServer.cpp). You can build it by following [these](/test/README.md) directions. The usage of the two key classes (CRtspSession and SimStreamer) are very similar to to the ESP32 usage. 69 | 70 | ## Supporting new camera devices 71 | 72 | Supporting new camera devices is quite simple. See OV2640Streamer for an example and implement streamImage() 73 | by reading a frame from your camera. 74 | 75 | # Structure and design notes 76 | 77 | # Known Issues 78 | 79 | ## Video is delayed 80 | 81 | Most players do a buffering. For example VLC by default sets a cache to 1000ms, resulting in a noticable delay. If you want to remove the delay set the `:network-caching=0` - in the UI it's hidden under `Show more options`. 82 | 83 | # Issues and sending pull requests 84 | 85 | Please report issues and send pull requests. I'll happily reply. ;-) 86 | 87 | # Credits 88 | 89 | The server code was initially based on a great 2013 [tutorial](https://www.medialan.de/usecase0001.html) by Medialan. 90 | 91 | # License 92 | 93 | Copyright 2018 S. Kevin Hester-Chow, kevinh@geeksville.com (MIT License) 94 | 95 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 96 | 97 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 98 | 99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 100 | -------------------------------------------------------------------------------- /src/OV2640.cpp: -------------------------------------------------------------------------------- 1 | #include "OV2640.h" 2 | 3 | #define TAG "OV2640" 4 | 5 | // definitions appropriate for the ESP32-CAM devboard (and most clones) 6 | camera_config_t esp32cam_config{ 7 | 8 | .pin_pwdn = -1, // FIXME: on the TTGO T-Journal I think this is GPIO 0 9 | .pin_reset = 15, 10 | 11 | .pin_xclk = 27, 12 | 13 | .pin_sscb_sda = 25, 14 | .pin_sscb_scl = 23, 15 | 16 | .pin_d7 = 19, 17 | .pin_d6 = 36, 18 | .pin_d5 = 18, 19 | .pin_d4 = 39, 20 | .pin_d3 = 5, 21 | .pin_d2 = 34, 22 | .pin_d1 = 35, 23 | .pin_d0 = 17, 24 | .pin_vsync = 22, 25 | .pin_href = 26, 26 | .pin_pclk = 21, 27 | .xclk_freq_hz = 20000000, 28 | .ledc_timer = LEDC_TIMER_0, 29 | .ledc_channel = LEDC_CHANNEL_0, 30 | .pixel_format = PIXFORMAT_JPEG, 31 | // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space 32 | // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer 33 | // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb 34 | .frame_size = FRAMESIZE_SVGA, 35 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 36 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 37 | }; 38 | 39 | camera_config_t esp32cam_aithinker_config{ 40 | 41 | .pin_pwdn = 32, 42 | .pin_reset = -1, 43 | 44 | .pin_xclk = 0, 45 | 46 | .pin_sscb_sda = 26, 47 | .pin_sscb_scl = 27, 48 | 49 | // Note: LED GPIO is apparently 4 not sure where that goes 50 | // per https://github.com/donny681/ESP32_CAMERA_QR/blob/e4ef44549876457cd841f33a0892c82a71f35358/main/led.c 51 | .pin_d7 = 35, 52 | .pin_d6 = 34, 53 | .pin_d5 = 39, 54 | .pin_d4 = 36, 55 | .pin_d3 = 21, 56 | .pin_d2 = 19, 57 | .pin_d1 = 18, 58 | .pin_d0 = 5, 59 | .pin_vsync = 25, 60 | .pin_href = 23, 61 | .pin_pclk = 22, 62 | .xclk_freq_hz = 20000000, 63 | .ledc_timer = LEDC_TIMER_1, 64 | .ledc_channel = LEDC_CHANNEL_1, 65 | .pixel_format = PIXFORMAT_JPEG, 66 | // .frame_size = FRAMESIZE_UXGA, // needs 234K of framebuffer space 67 | // .frame_size = FRAMESIZE_SXGA, // needs 160K for framebuffer 68 | // .frame_size = FRAMESIZE_XGA, // needs 96K or even smaller FRAMESIZE_SVGA - can work if using only 1 fb 69 | .frame_size = FRAMESIZE_SVGA, 70 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 71 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 72 | }; 73 | 74 | camera_config_t esp32cam_ttgo_t_config{ 75 | 76 | .pin_pwdn = 26, 77 | .pin_reset = -1, 78 | 79 | .pin_xclk = 32, 80 | 81 | .pin_sscb_sda = 13, 82 | .pin_sscb_scl = 12, 83 | 84 | .pin_d7 = 39, 85 | .pin_d6 = 36, 86 | .pin_d5 = 23, 87 | .pin_d4 = 18, 88 | .pin_d3 = 15, 89 | .pin_d2 = 4, 90 | .pin_d1 = 14, 91 | .pin_d0 = 5, 92 | .pin_vsync = 27, 93 | .pin_href = 25, 94 | .pin_pclk = 19, 95 | .xclk_freq_hz = 20000000, 96 | .ledc_timer = LEDC_TIMER_0, 97 | .ledc_channel = LEDC_CHANNEL_0, 98 | .pixel_format = PIXFORMAT_JPEG, 99 | .frame_size = FRAMESIZE_SVGA, 100 | .jpeg_quality = 12, //0-63 lower numbers are higher quality 101 | .fb_count = 2 // if more than one i2s runs in continous mode. Use only with jpeg 102 | }; 103 | void OV2640::done(void) 104 | { 105 | if (fb) { 106 | //return the frame buffer back to the driver for reuse 107 | esp_camera_fb_return(fb); 108 | fb = NULL; 109 | } 110 | } 111 | 112 | void OV2640::run(void) 113 | { 114 | if (fb) 115 | //return the frame buffer back to the driver for reuse 116 | esp_camera_fb_return(fb); 117 | 118 | fb = esp_camera_fb_get(); 119 | } 120 | 121 | void OV2640::runIfNeeded(void) 122 | { 123 | if (!fb) 124 | run(); 125 | } 126 | 127 | int OV2640::getWidth(void) 128 | { 129 | runIfNeeded(); 130 | return fb->width; 131 | } 132 | 133 | int OV2640::getHeight(void) 134 | { 135 | runIfNeeded(); 136 | return fb->height; 137 | } 138 | 139 | size_t OV2640::getSize(void) 140 | { 141 | runIfNeeded(); 142 | if (!fb) 143 | return 0; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? 144 | return fb->len; 145 | } 146 | 147 | uint8_t *OV2640::getfb(void) 148 | { 149 | runIfNeeded(); 150 | if (!fb) 151 | return NULL; // FIXME - this shouldn't be possible but apparently the new cam board returns null sometimes? 152 | 153 | return fb->buf; 154 | } 155 | 156 | framesize_t OV2640::getFrameSize(void) 157 | { 158 | return _cam_config.frame_size; 159 | } 160 | 161 | void OV2640::setFrameSize(framesize_t size) 162 | { 163 | _cam_config.frame_size = size; 164 | } 165 | 166 | pixformat_t OV2640::getPixelFormat(void) 167 | { 168 | return _cam_config.pixel_format; 169 | } 170 | 171 | void OV2640::setPixelFormat(pixformat_t format) 172 | { 173 | switch (format) 174 | { 175 | case PIXFORMAT_RGB565: 176 | case PIXFORMAT_YUV422: 177 | case PIXFORMAT_GRAYSCALE: 178 | case PIXFORMAT_JPEG: 179 | _cam_config.pixel_format = format; 180 | break; 181 | default: 182 | _cam_config.pixel_format = PIXFORMAT_GRAYSCALE; 183 | break; 184 | } 185 | } 186 | 187 | esp_err_t OV2640::init(camera_config_t config) 188 | { 189 | memset(&_cam_config, 0, sizeof(_cam_config)); 190 | memcpy(&_cam_config, &config, sizeof(config)); 191 | 192 | esp_err_t err = esp_camera_init(&_cam_config); 193 | if (err != ESP_OK) 194 | { 195 | printf("Camera probe failed with error 0x%x", err); 196 | return err; 197 | } 198 | // ESP_ERROR_CHECK(gpio_install_isr_service(0)); 199 | 200 | return ESP_OK; 201 | } 202 | -------------------------------------------------------------------------------- /examples/ESP32-devcam.ino: -------------------------------------------------------------------------------- 1 | #include "OV2640.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "SimStreamer.h" 7 | #include "OV2640Streamer.h" 8 | #include "CRtspSession.h" 9 | 10 | #define ENABLE_OLED //if want use oled ,turn on thi macro 11 | // #define SOFTAP_MODE // If you want to run our own softap turn this on 12 | #define ENABLE_WEBSERVER 13 | #define ENABLE_RTSPSERVER 14 | 15 | #ifdef ENABLE_OLED 16 | #include "SSD1306.h" 17 | #define OLED_ADDRESS 0x3c 18 | #define I2C_SDA 14 19 | #define I2C_SCL 13 20 | SSD1306Wire display(OLED_ADDRESS, I2C_SDA, I2C_SCL, GEOMETRY_128_32); 21 | bool hasDisplay; // we probe for the device at runtime 22 | #endif 23 | 24 | OV2640 cam; 25 | 26 | #ifdef ENABLE_WEBSERVER 27 | WebServer server(80); 28 | #endif 29 | 30 | #ifdef ENABLE_RTSPSERVER 31 | WiFiServer rtspServer(8554); 32 | #endif 33 | 34 | 35 | #ifdef SOFTAP_MODE 36 | IPAddress apIP = IPAddress(192, 168, 1, 1); 37 | #else 38 | #include "wifikeys.h" 39 | #endif 40 | 41 | #ifdef ENABLE_WEBSERVER 42 | void handle_jpg_stream(void) 43 | { 44 | WiFiClient client = server.client(); 45 | String response = "HTTP/1.1 200 OK\r\n"; 46 | response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n"; 47 | server.sendContent(response); 48 | 49 | while (1) 50 | { 51 | cam.run(); 52 | if (!client.connected()) 53 | break; 54 | response = "--frame\r\n"; 55 | response += "Content-Type: image/jpeg\r\n\r\n"; 56 | server.sendContent(response); 57 | 58 | client.write((char *)cam.getfb(), cam.getSize()); 59 | server.sendContent("\r\n"); 60 | if (!client.connected()) 61 | break; 62 | } 63 | } 64 | 65 | void handle_jpg(void) 66 | { 67 | WiFiClient client = server.client(); 68 | 69 | cam.run(); 70 | if (!client.connected()) 71 | { 72 | return; 73 | } 74 | String response = "HTTP/1.1 200 OK\r\n"; 75 | response += "Content-disposition: inline; filename=capture.jpg\r\n"; 76 | response += "Content-type: image/jpeg\r\n\r\n"; 77 | server.sendContent(response); 78 | client.write((char *)cam.getfb(), cam.getSize()); 79 | } 80 | 81 | void handleNotFound() 82 | { 83 | String message = "Server is running!\n\n"; 84 | message += "URI: "; 85 | message += server.uri(); 86 | message += "\nMethod: "; 87 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 88 | message += "\nArguments: "; 89 | message += server.args(); 90 | message += "\n"; 91 | server.send(200, "text/plain", message); 92 | } 93 | #endif 94 | 95 | #ifdef ENABLE_OLED 96 | #define LCD_MESSAGE(msg) lcdMessage(msg) 97 | #else 98 | #define LCD_MESSAGE(msg) 99 | #endif 100 | 101 | #ifdef ENABLE_OLED 102 | void lcdMessage(String msg) 103 | { 104 | if(hasDisplay) { 105 | display.clear(); 106 | display.drawString(128 / 2, 32 / 2, msg); 107 | display.display(); 108 | } 109 | } 110 | #endif 111 | 112 | CStreamer *streamer; 113 | 114 | void setup() 115 | { 116 | #ifdef ENABLE_OLED 117 | hasDisplay = display.init(); 118 | if(hasDisplay) { 119 | display.flipScreenVertically(); 120 | display.setFont(ArialMT_Plain_16); 121 | display.setTextAlignment(TEXT_ALIGN_CENTER); 122 | } 123 | #endif 124 | LCD_MESSAGE("booting"); 125 | 126 | Serial.begin(115200); 127 | while (!Serial) 128 | { 129 | ; 130 | } 131 | cam.init(esp32cam_config); 132 | 133 | IPAddress ip; 134 | 135 | 136 | #ifdef SOFTAP_MODE 137 | const char *hostname = "devcam"; 138 | // WiFi.hostname(hostname); // FIXME - find out why undefined 139 | LCD_MESSAGE("starting softAP"); 140 | WiFi.mode(WIFI_AP); 141 | WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); 142 | bool result = WiFi.softAP(hostname, "12345678", 1, 0); 143 | if (!result) 144 | { 145 | Serial.println("AP Config failed."); 146 | return; 147 | } 148 | else 149 | { 150 | Serial.println("AP Config Success."); 151 | Serial.print("AP MAC: "); 152 | Serial.println(WiFi.softAPmacAddress()); 153 | 154 | ip = WiFi.softAPIP(); 155 | } 156 | #else 157 | LCD_MESSAGE(String("join ") + ssid); 158 | WiFi.mode(WIFI_STA); 159 | WiFi.begin(ssid, password); 160 | while (WiFi.status() != WL_CONNECTED) 161 | { 162 | delay(500); 163 | Serial.print(F(".")); 164 | } 165 | ip = WiFi.localIP(); 166 | Serial.println(F("WiFi connected")); 167 | Serial.println(""); 168 | Serial.println(ip); 169 | #endif 170 | 171 | LCD_MESSAGE(ip.toString()); 172 | 173 | #ifdef ENABLE_WEBSERVER 174 | server.on("/", HTTP_GET, handle_jpg_stream); 175 | server.on("/jpg", HTTP_GET, handle_jpg); 176 | server.onNotFound(handleNotFound); 177 | server.begin(); 178 | #endif 179 | 180 | #ifdef ENABLE_RTSPSERVER 181 | rtspServer.begin(); 182 | 183 | //streamer = new SimStreamer(true); // our streamer for UDP/TCP based RTP transport 184 | streamer = new OV2640Streamer(cam); // our streamer for UDP/TCP based RTP transport 185 | #endif 186 | } 187 | 188 | void loop() 189 | { 190 | #ifdef ENABLE_WEBSERVER 191 | server.handleClient(); 192 | #endif 193 | 194 | #ifdef ENABLE_RTSPSERVER 195 | uint32_t msecPerFrame = 100; 196 | static uint32_t lastimage = millis(); 197 | 198 | // If we have an active client connection, just service that until gone 199 | streamer->handleRequests(0); // we don't use a timeout here, 200 | // instead we send only if we have new enough frames 201 | uint32_t now = millis(); 202 | if(streamer->anySessions()) { 203 | if(now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover 204 | streamer->streamImage(now); 205 | lastimage = now; 206 | 207 | // check if we are overrunning our max frame rate 208 | now = millis(); 209 | if(now > lastimage + msecPerFrame) { 210 | printf("warning exceeding max frame rate of %d ms\n", now - lastimage); 211 | } 212 | } 213 | } 214 | 215 | WiFiClient rtspClient = rtspServer.accept(); 216 | if(rtspClient) { 217 | Serial.print("client: "); 218 | Serial.print(rtspClient.remoteIP()); 219 | Serial.println(); 220 | streamer->addSession(rtspClient); 221 | } 222 | #endif 223 | } 224 | -------------------------------------------------------------------------------- /src/CStreamer.cpp: -------------------------------------------------------------------------------- 1 | #include "CStreamer.h" 2 | #include "CRtspSession.h" 3 | 4 | #include 5 | 6 | CStreamer::CStreamer(u_short width, u_short height) : m_Clients() 7 | { 8 | printf("Creating TSP streamer\n"); 9 | m_RtpServerPort = 0; 10 | m_RtcpServerPort = 0; 11 | 12 | m_SequenceNumber = 0; 13 | m_Timestamp = 0; 14 | m_SendIdx = 0; 15 | 16 | m_RtpSocket = NULLSOCKET; 17 | m_RtcpSocket = NULLSOCKET; 18 | 19 | m_width = width; 20 | m_height = height; 21 | m_prevMsec = 0; 22 | 23 | m_udpRefCount = 0; 24 | 25 | debug = false; 26 | 27 | m_URIHost = "127.0.0.1:554"; 28 | m_URIPresentation = "mjpeg"; 29 | m_URIStream = "1"; 30 | } 31 | 32 | CStreamer::~CStreamer() 33 | { 34 | LinkedListElement* element = m_Clients.m_Next; 35 | CRtspSession* session = NULL; 36 | while (element != &m_Clients) 37 | { 38 | session = static_cast(element); 39 | element = element->m_Next; 40 | delete session; 41 | } 42 | }; 43 | 44 | CRtspSession* CStreamer::addSession( SOCKET aClient ) 45 | { 46 | // if ( debug ) printf("CStreamer::addSession\n"); 47 | CRtspSession* session = new CRtspSession( aClient, this ); // our threads RTSP session and state 48 | // we have it stored in m_Clients 49 | session->debug = debug; 50 | return session; 51 | } 52 | 53 | void CStreamer::setURI( String hostport, String pres, String stream ) // set URI parts for sessions to use. 54 | { 55 | m_URIHost = hostport; 56 | m_URIPresentation = pres; 57 | m_URIStream = stream; 58 | } 59 | 60 | int CStreamer::SendRtpPacket(unsigned const char * jpeg, int jpegLen, int fragmentOffset, BufPtr quant0tbl, BufPtr quant1tbl) 61 | { 62 | // if ( debug ) printf("CStreamer::SendRtpPacket offset:%d - begin\n", fragmentOffset); 63 | #define KRtpHeaderSize 12 // size of the RTP header 64 | #define KJpegHeaderSize 8 // size of the special JPEG payload header 65 | 66 | #define MAX_FRAGMENT_SIZE 1100 // FIXME, pick more carefully 67 | int fragmentLen = MAX_FRAGMENT_SIZE; 68 | if(fragmentLen + fragmentOffset > jpegLen) // Shrink last fragment if needed 69 | fragmentLen = jpegLen - fragmentOffset; 70 | 71 | bool isLastFragment = (fragmentOffset + fragmentLen) == jpegLen; 72 | 73 | if (!m_Clients.NotEmpty()) 74 | { 75 | return isLastFragment ? 0 : fragmentOffset; 76 | } 77 | 78 | // Do we have custom quant tables? If so include them per RFC 79 | 80 | bool includeQuantTbl = quant0tbl && quant1tbl && fragmentOffset == 0; 81 | uint8_t q = includeQuantTbl ? 128 : 0x5e; 82 | 83 | static char RtpBuf[2048]; // Note: we assume single threaded, this large buf we keep off of the tiny stack 84 | int RtpPacketSize = fragmentLen + KRtpHeaderSize + KJpegHeaderSize + (includeQuantTbl ? (4 + 64 * 2) : 0); 85 | 86 | memset(RtpBuf,0x00,sizeof(RtpBuf)); 87 | // Prepare the first 4 byte of the packet. This is the Rtp over Rtsp header in case of TCP based transport 88 | RtpBuf[0] = '$'; // magic number 89 | RtpBuf[1] = 0; // number of multiplexed subchannel on RTPS connection - here the RTP channel 90 | RtpBuf[2] = (RtpPacketSize & 0x0000FF00) >> 8; 91 | RtpBuf[3] = (RtpPacketSize & 0x000000FF); 92 | // Prepare the 12 byte RTP header 93 | RtpBuf[4] = 0x80; // RTP version 94 | RtpBuf[5] = 0x1a | (isLastFragment ? 0x80 : 0x00); // JPEG payload (26) and marker bit 95 | RtpBuf[7] = m_SequenceNumber & 0x0FF; // each packet is counted with a sequence counter 96 | RtpBuf[6] = m_SequenceNumber >> 8; 97 | RtpBuf[8] = (m_Timestamp & 0xFF000000) >> 24; // each image gets a timestamp 98 | RtpBuf[9] = (m_Timestamp & 0x00FF0000) >> 16; 99 | RtpBuf[10] = (m_Timestamp & 0x0000FF00) >> 8; 100 | RtpBuf[11] = (m_Timestamp & 0x000000FF); 101 | RtpBuf[12] = 0x13; // 4 byte SSRC (sychronization source identifier) 102 | RtpBuf[13] = 0xf9; // we just an arbitrary number here to keep it simple 103 | RtpBuf[14] = 0x7e; 104 | RtpBuf[15] = 0x67; 105 | 106 | // Prepare the 8 byte payload JPEG header 107 | RtpBuf[16] = 0x00; // type specific 108 | RtpBuf[17] = (fragmentOffset & 0x00FF0000) >> 16; // 3 byte fragmentation offset for fragmented images 109 | RtpBuf[18] = (fragmentOffset & 0x0000FF00) >> 8; 110 | RtpBuf[19] = (fragmentOffset & 0x000000FF); 111 | 112 | /* These sampling factors indicate that the chrominance components of 113 | type 0 video is downsampled horizontally by 2 (often called 4:2:2) 114 | while the chrominance components of type 1 video are downsampled both 115 | horizontally and vertically by 2 (often called 4:2:0). */ 116 | RtpBuf[20] = 0x00; // type (fixme might be wrong for camera data) https://tools.ietf.org/html/rfc2435 117 | RtpBuf[21] = q; // quality scale factor was 0x5e 118 | RtpBuf[22] = m_width / 8; // width / 8 119 | RtpBuf[23] = m_height / 8; // height / 8 120 | 121 | int headerLen = 24; // Inlcuding jpeg header but not qant table header 122 | if(includeQuantTbl) { // we need a quant header - but only in first packet of the frame 123 | //if ( debug ) printf("inserting quanttbl\n"); 124 | RtpBuf[24] = 0; // MBZ 125 | RtpBuf[25] = 0; // 8 bit precision 126 | RtpBuf[26] = 0; // MSB of lentgh 127 | 128 | int numQantBytes = 64; // Two 64 byte tables 129 | RtpBuf[27] = 2 * numQantBytes; // LSB of length 130 | 131 | headerLen += 4; 132 | 133 | memcpy(RtpBuf + headerLen, quant0tbl, numQantBytes); 134 | headerLen += numQantBytes; 135 | 136 | memcpy(RtpBuf + headerLen, quant1tbl, numQantBytes); 137 | headerLen += numQantBytes; 138 | } 139 | // if ( debug ) printf("Sending timestamp %d, seq %d, fragoff %d, fraglen %d, jpegLen %d\n", m_Timestamp, m_SequenceNumber, fragmentOffset, fragmentLen, jpegLen); 140 | 141 | // append the JPEG scan data to the RTP buffer 142 | memcpy(RtpBuf + headerLen,jpeg + fragmentOffset, fragmentLen); 143 | fragmentOffset += fragmentLen; 144 | 145 | m_SequenceNumber++; // prepare the packet counter for the next packet 146 | 147 | IPADDRESS otherip; 148 | IPPORT otherport; 149 | 150 | // RTP marker bit must be set on last fragment 151 | LinkedListElement* element = m_Clients.m_Next; 152 | CRtspSession* session = NULL; 153 | while (element != &m_Clients) 154 | { 155 | session = static_cast(element); 156 | if (session->m_streaming && !session->m_stopped) { 157 | if (session->isTcpTransport()) // RTP over RTSP - we send the buffer + 4 byte additional header 158 | socketsend(session->getClient(),RtpBuf,RtpPacketSize + 4); 159 | else // UDP - we send just the buffer by skipping the 4 byte RTP over RTSP header 160 | { 161 | socketpeeraddr(session->getClient(), &otherip, &otherport); 162 | udpsocketsend(m_RtpSocket,&RtpBuf[4],RtpPacketSize, otherip, session->getRtpClientPort()); 163 | } 164 | } 165 | element = element->m_Next; 166 | } 167 | // if ( debug ) printf("CStreamer::SendRtpPacket offset:%d - end\n", fragmentOffset); 168 | return isLastFragment ? 0 : fragmentOffset; 169 | }; 170 | 171 | u_short CStreamer::GetRtpServerPort() 172 | { 173 | return m_RtpServerPort; 174 | }; 175 | 176 | u_short CStreamer::GetRtcpServerPort() 177 | { 178 | return m_RtcpServerPort; 179 | }; 180 | 181 | bool CStreamer::InitUdpTransport(void) 182 | { 183 | if (m_udpRefCount != 0) 184 | { 185 | ++m_udpRefCount; 186 | return true; 187 | } 188 | 189 | for (u_short P = 6970; P < 0xFFFE; P += 2) 190 | { 191 | m_RtpSocket = udpsocketcreate(P); 192 | if (m_RtpSocket) 193 | { // Rtp socket was bound successfully. Lets try to bind the consecutive Rtsp socket 194 | m_RtcpSocket = udpsocketcreate(P + 1); 195 | if (m_RtcpSocket) 196 | { 197 | m_RtpServerPort = P; 198 | m_RtcpServerPort = P+1; 199 | break; 200 | } 201 | else 202 | { 203 | udpsocketclose(m_RtpSocket); 204 | udpsocketclose(m_RtcpSocket); 205 | }; 206 | } 207 | }; 208 | ++m_udpRefCount; 209 | return true; 210 | } 211 | 212 | void CStreamer::ReleaseUdpTransport(void) 213 | { 214 | --m_udpRefCount; 215 | if (m_udpRefCount == 0) 216 | { 217 | m_RtpServerPort = 0; 218 | m_RtcpServerPort = 0; 219 | udpsocketclose(m_RtpSocket); 220 | udpsocketclose(m_RtcpSocket); 221 | 222 | m_RtpSocket = NULLSOCKET; 223 | m_RtcpSocket = NULLSOCKET; 224 | } 225 | } 226 | 227 | /** 228 | Call handleRequests on all sessions 229 | */ 230 | bool CStreamer::handleRequests(uint32_t readTimeoutMs) 231 | { 232 | bool retVal = true; 233 | LinkedListElement* element = m_Clients.m_Next; 234 | while(element != &m_Clients) 235 | { 236 | CRtspSession* session = static_cast(element); 237 | retVal &= session->handleRequests(readTimeoutMs); 238 | 239 | element = element->m_Next; 240 | 241 | if (session->m_stopped) 242 | { 243 | // remove session here, so we wont have to send to it 244 | delete session; 245 | } 246 | } 247 | 248 | return retVal; 249 | } 250 | 251 | void CStreamer::streamFrame(unsigned const char *data, uint32_t dataLen, uint32_t curMsec) 252 | { 253 | if(m_prevMsec == 0) // first frame init our timestamp 254 | m_prevMsec = curMsec; 255 | 256 | // compute deltat (being careful to handle clock rollover with a little lie) 257 | uint32_t deltams = (curMsec >= m_prevMsec) ? curMsec - m_prevMsec : 100; 258 | m_prevMsec = curMsec; 259 | 260 | // locate quant tables if possible 261 | BufPtr qtable0, qtable1; 262 | 263 | if(!decodeJPEGfile(&data, &dataLen, &qtable0, &qtable1)) { 264 | printf("can't decode jpeg data\n"); 265 | return; 266 | } 267 | 268 | int offset = 0; 269 | do { 270 | offset = SendRtpPacket(data, dataLen, offset, qtable0, qtable1); 271 | } while(offset != 0); 272 | 273 | // Increment ONLY after a full frame 274 | uint32_t units = 90000; // Hz per RFC 2435 275 | m_Timestamp += (units * deltams / 1000); // fixed timestamp increment for a frame rate of 25fps 276 | 277 | m_SendIdx++; 278 | if (m_SendIdx > 1) m_SendIdx = 0; 279 | }; 280 | 281 | #include 282 | 283 | // search for a particular JPEG marker, moves *start to just after that marker 284 | // This function fixes up the provided start ptr to point to the 285 | // actual JPEG stream data and returns the number of bytes skipped 286 | // APP0 e0 287 | // DQT db 288 | // DQT db 289 | // DHT c4 290 | // DHT c4 291 | // DHT c4 292 | // DHT c4 293 | // SOF0 c0 baseline (not progressive) 3 color 0x01 Y, 0x21 2h1v, 0x00 tbl0 294 | // - 0x02 Cb, 0x11 1h1v, 0x01 tbl1 - 0x03 Cr, 0x11 1h1v, 0x01 tbl1 295 | // therefore 4:2:2, with two separate quant tables (0 and 1) 296 | // SOS da 297 | // EOI d9 (no need to strip data after this RFC says client will discard) 298 | bool findJPEGheader(BufPtr *start, uint32_t *len, uint8_t marker) { 299 | // per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format 300 | unsigned const char *bytes = *start; 301 | 302 | // kinda skanky, will break if unlucky and the headers inxlucde 0xffda 303 | // might fall off array if jpeg is invalid 304 | // FIXME - return false instead 305 | while(bytes - *start < *len) { 306 | uint8_t framing = *bytes++; // better be 0xff 307 | if(framing != 0xff) { 308 | printf("malformed jpeg, framing=%x\n", framing); 309 | return false; 310 | } 311 | uint8_t typecode = *bytes++; 312 | if(typecode == marker) { 313 | unsigned skipped = bytes - *start; 314 | //if ( debug ) printf("found marker 0x%x, skipped %d\n", marker, skipped); 315 | 316 | *start = bytes; 317 | 318 | // shrink len for the bytes we just skipped 319 | *len -= skipped; 320 | 321 | return true; 322 | } 323 | else { 324 | // not the section we were looking for, skip the entire section 325 | switch(typecode) { 326 | case 0xd8: // start of image 327 | { 328 | break; // no data to skip 329 | } 330 | case 0xe0: // app0 331 | case 0xdb: // dqt 332 | case 0xc4: // dht 333 | case 0xc0: // sof0 334 | case 0xda: // sos 335 | { 336 | // standard format section with 2 bytes for len. skip that many bytes 337 | uint32_t len = bytes[0] * 256 + bytes[1]; 338 | //if ( debug ) printf("skipping section 0x%x, %d bytes\n", typecode, len); 339 | bytes += len; 340 | break; 341 | } 342 | default: 343 | printf("unexpected jpeg typecode 0x%x\n", typecode); 344 | break; 345 | } 346 | } 347 | } 348 | 349 | printf("failed to find jpeg marker 0x%x", marker); 350 | return false; 351 | } 352 | 353 | // the scan data uses byte stuffing to guarantee anything that starts with 0xff 354 | // followed by something not zero, is a new section. Look for that marker and return the ptr 355 | // pointing there 356 | void skipScanBytes(BufPtr *start) { 357 | BufPtr bytes = *start; 358 | 359 | while(true) { // FIXME, check against length 360 | while(*bytes++ != 0xff); 361 | if(*bytes++ != 0) { 362 | *start = bytes - 2; // back up to the 0xff marker we just found 363 | return; 364 | } 365 | } 366 | } 367 | void nextJpegBlock(BufPtr *bytes) { 368 | uint32_t len = (*bytes)[0] * 256 + (*bytes)[1]; 369 | //if ( debug ) printf("going to next jpeg block %d bytes\n", len); 370 | *bytes += len; 371 | } 372 | 373 | // When JPEG is stored as a file it is wrapped in a container 374 | // This function fixes up the provided start ptr to point to the 375 | // actual JPEG stream data and returns the number of bytes skipped 376 | bool decodeJPEGfile(BufPtr *start, uint32_t *len, BufPtr *qtable0, BufPtr *qtable1) { 377 | // per https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format 378 | unsigned const char *bytes = *start; 379 | 380 | if(!findJPEGheader(&bytes, len, 0xd8)) // better at least look like a jpeg file 381 | return false; // FAILED! 382 | 383 | // Look for quant tables if they are present 384 | *qtable0 = NULL; 385 | *qtable1 = NULL; 386 | BufPtr quantstart = *start; 387 | uint32_t quantlen = *len; 388 | if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) { 389 | printf("error can't find quant table 0\n"); 390 | } 391 | else { 392 | // if ( debug ) printf("found quant table %x\n", quantstart[2]); 393 | 394 | *qtable0 = quantstart + 3; // 3 bytes of header skipped 395 | nextJpegBlock(&quantstart); 396 | if(!findJPEGheader(&quantstart, &quantlen, 0xdb)) { 397 | printf("error can't find quant table 1\n"); 398 | } 399 | else { 400 | // if ( debug ) printf("found quant table %x\n", quantstart[2]); 401 | } 402 | *qtable1 = quantstart + 3; 403 | nextJpegBlock(&quantstart); 404 | } 405 | 406 | if(!findJPEGheader(start, len, 0xda)) 407 | return false; // FAILED! 408 | 409 | // Skip the header bytes of the SOS marker FIXME why doesn't this work? 410 | uint32_t soslen = (*start)[0] * 256 + (*start)[1]; 411 | *start += soslen; 412 | *len -= soslen; 413 | 414 | // start scanning the data portion of the scan to find the end marker 415 | BufPtr endmarkerptr = *start; 416 | uint32_t endlen = *len; 417 | 418 | skipScanBytes(&endmarkerptr); 419 | if(!findJPEGheader(&endmarkerptr, &endlen, 0xd9)) 420 | return false; // FAILED! 421 | 422 | // endlen must now be the # of bytes between the start of our scan and 423 | // the end marker, tell the caller to ignore bytes afterwards 424 | *len = endmarkerptr - *start; 425 | 426 | return true; 427 | } 428 | -------------------------------------------------------------------------------- /src/CRtspSession.cpp: -------------------------------------------------------------------------------- 1 | #include "CRtspSession.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | //=========================================================== 8 | //=========================================================== 9 | //=========================================================== 10 | CRtspSession::CRtspSession(SOCKET aClient, CStreamer * aStreamer) : LinkedListElement(aStreamer->getClientsListHead()), 11 | m_Client(aClient), 12 | m_Streamer(aStreamer) 13 | { 14 | printf("Creating RTSP session\n"); 15 | newCommandInit(); 16 | 17 | m_RtspClient = m_Client; 18 | m_RtspSessionID = getRandom(); // create a session ID 19 | m_RtspSessionID |= 0x80000000; 20 | m_StreamID = -1; 21 | m_ClientRTPPort = 0; 22 | m_ClientRTCPPort = 0; 23 | m_TcpTransport = false; 24 | m_streaming = false; 25 | m_stopped = false; 26 | 27 | m_RtpClientPort = 0; 28 | m_RtcpClientPort = 0; 29 | 30 | m_CSeq = 0; // CSeq sequense must be kept through the whole session 31 | m_RtspCmdType = RTSP_UNKNOWN; 32 | debug = false; 33 | } 34 | 35 | CRtspSession::~CRtspSession() 36 | { 37 | m_Streamer->ReleaseUdpTransport(); 38 | closesocket(m_RtspClient); 39 | } 40 | 41 | /*! @brief Initialize stuff for processing new client's command */ 42 | void CRtspSession::newCommandInit() 43 | { 44 | memset( m_CommandPresentationPart, 0x00, sizeof( m_CommandPresentationPart ) ); 45 | memset( m_CommandStreamPart, 0x00, sizeof( m_CommandStreamPart ) ); 46 | memset( m_CommandHostPort, 0x00, sizeof( m_CommandHostPort ) ); 47 | m_ContentLength = 0; 48 | } 49 | 50 | /*! @brief read numeric stuff after header name and check all possible sanity 51 | @param buf source buffer 52 | @param number the number 53 | @param max_length length of buf 54 | @return NULL if error or pointer to the rest of line 55 | */ 56 | static char * parse_numeric_header( char *buf, unsigned int *number, int max_length ) 57 | { 58 | int count = max_length; 59 | 60 | while ( *buf && count > 0 && ( *buf == ' ' || *buf == '\t' ) ) // skipping space after ':' 61 | { 62 | ++buf; 63 | --count; 64 | } 65 | 66 | if ( ! *buf || ! isdigit( *buf ) || ! count ) 67 | return NULL; 68 | 69 | char *number_start = buf; 70 | 71 | while( *buf && isdigit( *buf ) && count > 0 ) 72 | { 73 | ++buf; 74 | --count; 75 | } 76 | 77 | if ( count == 0 ) 78 | return NULL; 79 | 80 | char c = *buf; 81 | 82 | *buf = '\0'; 83 | *number = atoi( number_start ); 84 | *buf = c; 85 | 86 | return buf; 87 | } 88 | 89 | /*! @brief Called internally to fully parse new command from the client */ 90 | bool CRtspSession::ParseRtspRequest( char * aRequest, unsigned aRequestSize ) 91 | { 92 | static char CmdName[20]; // used for reporting only. longest cmd is like GET_PARAMETER == 13 char 93 | 94 | newCommandInit(); 95 | 96 | /* now our typical command will be like: 97 | [CRLF] 98 | SETUP rtsp://server.example.com/mjpeg/1 RTSP/1.0 99 | CSeq: 2 100 | Transport: RTP/AVP;unicast;something; 101 | client_port=7000-7001;somethingelse 102 | CRLF 103 | but we will use a required subset from rfc2326 as per Table 2 (https://tools.ietf.org/html/rfc2326#section-10) 104 | */ 105 | 106 | char *cur_pos = aRequest; 107 | int dst_pos = 0; // will reuse this to copy some parts into internal variables 108 | 109 | // 1st doing basic sanity check and URI parsing 110 | while ( dst_pos < 19 && *cur_pos != ' ' && *cur_pos != '\t' ) // skip possible CRLF and command name as we alredy got it in the handleRequests() 111 | { 112 | CmdName[ dst_pos++ ] = *(cur_pos++); 113 | } 114 | 115 | CmdName[ dst_pos ] = '\0'; 116 | 117 | while ( *cur_pos && isspace( *cur_pos ) ) 118 | ++cur_pos; 119 | 120 | if ( ! *cur_pos || 0 != strncasecmp( "rtsp://", cur_pos, 7 ) ) 121 | return false; 122 | 123 | cur_pos += 7; 124 | 125 | // getting host:port 126 | for( dst_pos = 0; *cur_pos && ! isspace ( *cur_pos ) && *cur_pos != '/'; ++cur_pos, ++dst_pos ) 127 | { 128 | if ( dst_pos == MAX_HOSTNAME_LEN ) 129 | return false; 130 | 131 | m_CommandHostPort[ dst_pos ] = *cur_pos; 132 | } 133 | 134 | if ( *cur_pos != '/' ) // no next part 135 | return false; 136 | 137 | m_CommandHostPort[ dst_pos ] = '\0'; 138 | if ( debug ) printf( "host-port: %s\n", m_CommandHostPort ); 139 | 140 | while ( *cur_pos == '/' ) 141 | ++cur_pos; 142 | 143 | // getting presentation part 144 | for( dst_pos = 0; *cur_pos && ! isspace ( *cur_pos ) && *cur_pos != '/'; ++cur_pos, ++dst_pos ) 145 | { 146 | if ( dst_pos == RTSP_PARAM_STRING_MAX ) 147 | return false; 148 | 149 | m_CommandPresentationPart[ dst_pos ] = *cur_pos; 150 | } 151 | 152 | if ( *cur_pos != '/' ) // no next part 153 | return false; 154 | 155 | m_CommandPresentationPart[ dst_pos ] = '\0'; 156 | if ( debug ) printf( "+ pres: %s\n", m_CommandPresentationPart ); 157 | 158 | while ( *cur_pos == '/' ) 159 | ++cur_pos; 160 | 161 | // getting stream part 162 | for( dst_pos = 0; *cur_pos && ! isspace ( *cur_pos ) && *cur_pos != '/'; ++cur_pos, ++dst_pos ) 163 | { 164 | if ( dst_pos == RTSP_PARAM_STRING_MAX ) 165 | return false; 166 | 167 | m_CommandStreamPart[ dst_pos ] = *cur_pos; 168 | } 169 | 170 | m_CommandStreamPart[ dst_pos ] = '\0'; 171 | 172 | while ( *cur_pos == '/' ) // vlc sometimes put extra / after session name on setup 173 | ++cur_pos; 174 | 175 | if ( *cur_pos != ' ' && *cur_pos != '\t' ) // no final RTSP/x.x 176 | return false; 177 | 178 | if ( debug ) printf( "+ stream: %s\n", m_CommandStreamPart ); 179 | 180 | while ( isspace( *cur_pos ) ) 181 | ++cur_pos; 182 | 183 | if ( 0 != strncmp( "RTSP/", cur_pos, 5 ) ) 184 | return false; 185 | 186 | cur_pos += 5; 187 | if ( ! isdigit( *cur_pos ) || cur_pos[ 1 ] != '.' || ! isdigit( cur_pos[ 2 ] ) ) 188 | return false; 189 | 190 | cur_pos += 3; 191 | 192 | // now looping through header lines and picking up what matter to us 193 | int left; // rough estimate of buffer space left to examine. 194 | // note that initial reader already put \0 mark in the buffer somewhere, so we only need to carefully check for it 195 | if ( debug ) printf( "### analyzing headers\n" ); 196 | 197 | for(;;) 198 | { 199 | // skipping leftovers from previous line 200 | while ( *cur_pos && *cur_pos != '\r' && cur_pos[ 1 ] != '\n' ) 201 | ++cur_pos; 202 | 203 | // at the end of headers block there must be CR,LF,CR,LF always, then either the body or \0 204 | if ( ! *cur_pos || ( *cur_pos != '\r' && cur_pos[ 1 ] != '\n' ) ) // still some unexpected garbage? 205 | return false; 206 | 207 | cur_pos += 2; // skip CRLF 208 | 209 | if ( ! *cur_pos ) // we're done with headers 210 | break; 211 | 212 | left = aRequestSize - ( cur_pos - aRequest ); 213 | 214 | // we're at the begin of the next header line now 215 | if ( debug ) // a little window to our current line beginning 216 | { 217 | printf( "* left: %d: '", left ); 218 | for( char *s = cur_pos; *s && (s - cur_pos) < 20; ++s) 219 | if( *s == '\r' ) 220 | printf ( "" ); 221 | else if( *s == '\n' ) 222 | printf ( "" ); 223 | else if ( isprint( *s ) ) 224 | putchar( *s ); 225 | else 226 | printf( "<0x%x>", *s ); 227 | puts( "'" ); 228 | } 229 | 230 | // now we're at the start of another header's line 231 | 232 | if ( 0 == strncmp( "CSeq:", cur_pos, 5) ) 233 | { 234 | unsigned new_cseq; 235 | 236 | left -= 5; 237 | cur_pos = parse_numeric_header( cur_pos + 5, &new_cseq, left ); 238 | 239 | if( cur_pos == NULL ) 240 | return false; 241 | 242 | m_CSeq = new_cseq; // we may check something here or later maybe... 243 | 244 | if ( debug ) printf( "+ got cseq: %u\n", new_cseq ); 245 | 246 | continue; // loop to next line 247 | } 248 | 249 | if ( 0 == strncmp( "Content-Length:", cur_pos, 15) ) 250 | { 251 | left -= 15; 252 | cur_pos = parse_numeric_header( cur_pos + 15, &m_ContentLength, left ); 253 | 254 | if( cur_pos == NULL ) 255 | return false; 256 | 257 | if ( debug ) printf( "+ got cont-len: %u\n", m_ContentLength ); 258 | 259 | continue; // loop to next line 260 | } 261 | 262 | // for other headers we gluing continued strings together to simplify analysis 263 | for ( char *p = cur_pos; *p; ++p ) 264 | { 265 | // peeking past CRLF: if there is space - it is continued header line 266 | if ( *p == '\r' && p[ 1 ] == '\n' ) 267 | { 268 | if ( p[ 2 ] != ' ' && p[ 2 ] != '\t' ) // no space. ending search 269 | break; 270 | 271 | // clearing and looking for another continuation 272 | *p = ' '; 273 | ++p; 274 | *p = ' '; 275 | } 276 | } 277 | 278 | // transport settings: proto, ports, etc 279 | if ( m_RtspCmdType == RTSP_SETUP && 0 == strncmp( "Transport:", cur_pos, 10 ) ) 280 | { 281 | cur_pos += 10; 282 | while( *cur_pos && isspace( *cur_pos ) ) 283 | ++cur_pos; 284 | 285 | if ( 0 != strncmp( cur_pos, "RTP/AVP", 7) ) // std says this is mandatory part 286 | return false; 287 | 288 | cur_pos += 7; 289 | 290 | if ( 0 == strncmp( cur_pos, "/TCP", 4 ) ) // TCP is also good? 291 | { 292 | m_TcpTransport = true; 293 | cur_pos += 4; 294 | } 295 | else 296 | m_TcpTransport = false; 297 | 298 | if ( debug ) printf( "+ Transport is %s\n", (m_TcpTransport ? "TCP" : "UDP") ); 299 | 300 | m_ClientRTPPort = 0; 301 | 302 | // now looking for sub-params like clent_port= 303 | char *next_part, last_char; 304 | for(;;) 305 | { 306 | while( *cur_pos == ';' || *cur_pos == ' ' || *cur_pos == '\t' ) 307 | ++cur_pos; 308 | 309 | if ( ! *cur_pos ) 310 | return false; 311 | 312 | if ( *cur_pos == '\r' && cur_pos[ 1 ] == '\n' ) 313 | break; 314 | 315 | next_part = strpbrk( cur_pos, ";\r" ); // gettin pointer to the next sub-param if any 316 | if( ! next_part ) 317 | return false; 318 | 319 | last_char = *next_part; // in case we'll need to put \0 here 320 | 321 | if ( 0 == strncmp( cur_pos, "client_port=", 12 ) ) // "client_port" "=" port [ "-" port ] 322 | { 323 | char *p = ( cur_pos += 12 ); 324 | while( isdigit( *p ) ) 325 | ++p; 326 | 327 | if ( p == cur_pos ) 328 | return false; 329 | 330 | *p = '\0'; 331 | 332 | m_ClientRTPPort = atoi( cur_pos ); 333 | m_ClientRTCPPort = m_ClientRTPPort + 1; 334 | if ( debug ) printf( "+ got client port: %u\n", m_ClientRTPPort ); 335 | } 336 | 337 | *next_part = last_char; // restoring if changed 338 | 339 | cur_pos = next_part; 340 | } 341 | } // Transport: 342 | 343 | if ( debug && *cur_pos != '\r' ) printf( "? unknown header ?\n" ); 344 | 345 | // ignored headers are skipped. we left current position at the CRLF so next loop is going smoothly 346 | while ( *cur_pos && *cur_pos != '\r' ) 347 | ++cur_pos; 348 | } // loop though headers 349 | 350 | printf( "\n+ RTSP command: %s\n", CmdName ); 351 | 352 | return true; 353 | } 354 | 355 | RTSP_CMD_TYPES CRtspSession::Handle_RtspRequest( char *aRequest, unsigned aRequestSize ) 356 | { 357 | if ( ParseRtspRequest( aRequest, aRequestSize ) ) 358 | { 359 | switch ( m_RtspCmdType ) 360 | { 361 | case RTSP_OPTIONS: Handle_RtspOPTION(); break; 362 | case RTSP_DESCRIBE: Handle_RtspDESCRIBE(); break; 363 | case RTSP_SETUP: Handle_RtspSETUP(); break; 364 | case RTSP_PLAY: Handle_RtspPLAY(); break; 365 | default: break; 366 | } 367 | } 368 | 369 | return m_RtspCmdType; 370 | } 371 | 372 | void CRtspSession::Handle_RtspOPTION() 373 | { 374 | static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack 375 | 376 | snprintf(Response,sizeof(Response), 377 | "RTSP/1.0 200 OK\r\nCSeq: %u\r\n" 378 | "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n\r\n", m_CSeq); 379 | 380 | socketsend(m_RtspClient,Response,strlen(Response)); 381 | } 382 | 383 | void CRtspSession::Handle_RtspDESCRIBE() // FIXME: too much redundancy. should eliminate intermediate buffers. 384 | { 385 | static char Response[1024]; // Note: we assume single threaded, this large buf we keep off of the tiny stack 386 | static char SDPBuf[1024]; 387 | static char URLBuf[1024]; 388 | 389 | // check whether we know a stream with the URL which is requested 390 | m_StreamID = -1; // invalid URL 391 | 392 | if ( m_Streamer->getURIPresentation() == m_CommandPresentationPart && 393 | m_Streamer->getURIStream() == m_CommandStreamPart ) 394 | m_StreamID = 0; 395 | 396 | if ( m_StreamID == -1 ) 397 | { // Stream not available 398 | snprintf( Response, sizeof(Response), 399 | "RTSP/1.0 404 Stream Not Found\r\nCSeq: %u\r\n%s\r\n", 400 | m_CSeq, 401 | DateHeader()); 402 | 403 | socketsend( m_RtspClient, Response, strlen(Response) ); 404 | return; 405 | } 406 | 407 | // simulate DESCRIBE server response 408 | static char OBuf[256]; 409 | char * ColonPtr; 410 | strcpy( OBuf, m_CommandHostPort ); 411 | ColonPtr = strstr( OBuf, ":" ); 412 | if (ColonPtr != nullptr) ColonPtr[0] = 0x00; 413 | 414 | snprintf( SDPBuf, sizeof(SDPBuf), 415 | "v=0\r\n" 416 | "o=- %d 1 IN IP4 %s\r\n" 417 | "s=\r\n" 418 | "t=0 0\r\n" // start / stop - 0 -> unbounded and permanent session 419 | "m=video 0 RTP/AVP 26\r\n" // currently we just handle UDP sessions (??????) 420 | // "a=x-dimensions: 640,480\r\n" 421 | "c=IN IP4 0.0.0.0\r\n", 422 | rand(), 423 | OBuf ); 424 | 425 | snprintf( URLBuf, sizeof(URLBuf), 426 | "rtsp://%s/%s/%s", m_CommandHostPort, m_CommandPresentationPart, m_CommandStreamPart ); 427 | 428 | snprintf( Response, sizeof(Response), 429 | "RTSP/1.0 200 OK\r\nCSeq: %u\r\n" 430 | "%s\r\n" 431 | "Content-Base: %s/\r\n" 432 | "Content-Type: application/sdp\r\n" 433 | "Content-Length: %d\r\n\r\n" 434 | "%s", 435 | m_CSeq, 436 | DateHeader(), 437 | URLBuf, 438 | (int) strlen(SDPBuf), 439 | SDPBuf); 440 | 441 | socketsend( m_RtspClient, Response, strlen(Response) ); 442 | } 443 | 444 | void CRtspSession::InitTransport(u_short aRtpPort, u_short aRtcpPort) 445 | { 446 | m_RtpClientPort = aRtpPort; 447 | m_RtcpClientPort = aRtcpPort; 448 | 449 | if (!m_TcpTransport) 450 | { // allocate port pairs for RTP/RTCP ports in UDP transport mode 451 | m_Streamer->InitUdpTransport(); 452 | }; 453 | }; 454 | 455 | void CRtspSession::Handle_RtspSETUP() 456 | { 457 | static char Response[1024]; 458 | static char Transport[255]; 459 | 460 | // init RTSP Session transport type (UDP or TCP) and ports for UDP transport 461 | InitTransport(m_ClientRTPPort,m_ClientRTCPPort); 462 | 463 | // simulate SETUP server response 464 | if (m_TcpTransport) 465 | snprintf(Transport,sizeof(Transport),"RTP/AVP/TCP;unicast;interleaved=0-1"); 466 | else 467 | snprintf(Transport,sizeof(Transport), 468 | "RTP/AVP;unicast;destination=127.0.0.1;source=127.0.0.1;client_port=%i-%i;server_port=%i-%i", 469 | m_ClientRTPPort, 470 | m_ClientRTCPPort, 471 | m_Streamer->GetRtpServerPort(), 472 | m_Streamer->GetRtcpServerPort()); 473 | snprintf(Response,sizeof(Response), 474 | "RTSP/1.0 200 OK\r\nCSeq: %u\r\n" 475 | "%s\r\n" 476 | "Transport: %s\r\n" 477 | "Session: %i\r\n\r\n", 478 | m_CSeq, 479 | DateHeader(), 480 | Transport, 481 | m_RtspSessionID); 482 | 483 | socketsend(m_RtspClient,Response,strlen(Response)); 484 | } 485 | 486 | void CRtspSession::Handle_RtspPLAY() 487 | { 488 | static char Response[1024]; 489 | 490 | // simulate SETUP server response 491 | snprintf( Response, sizeof(Response), 492 | "RTSP/1.0 200 OK\r\nCSeq: %u\r\n" 493 | "%s\r\n" 494 | "Range: npt=0.000-\r\n" 495 | "Session: %i\r\n" 496 | "RTP-Info: url=rtsp://127.0.0.1:8554/mjpeg/1/track1\r\n\r\n", // FIXME 497 | m_CSeq, 498 | DateHeader(), 499 | m_RtspSessionID); 500 | 501 | socketsend(m_RtspClient,Response,strlen(Response)); 502 | } 503 | 504 | char const * CRtspSession::DateHeader() 505 | { 506 | static char buf[200]; 507 | time_t tt = time(NULL); 508 | strftime(buf, sizeof buf, "Date: %a, %b %d %Y %H:%M:%S GMT", gmtime(&tt)); 509 | return buf; 510 | } 511 | 512 | int CRtspSession::GetStreamID() 513 | { 514 | return m_StreamID; 515 | }; 516 | 517 | /** 518 | Read from our socket, parsing commands as possible. 519 | */ 520 | bool CRtspSession::handleRequests( uint32_t readTimeoutMs ) 521 | { 522 | if ( m_stopped ) 523 | return false; // Already closed down 524 | 525 | static unsigned bufPos = 0; // current position into receiving buffer. used to glue split requests. 526 | static enum { hdrStateUnknown, hdrStateGotMethod, hdrStateInvalid } state = hdrStateUnknown; 527 | static char RecvBuf[RTSP_BUFFER_SIZE]; // Note: we assume single threaded, this large buf we keep off of the tiny stack 528 | 529 | if ( bufPos == 0 || bufPos >= sizeof( RecvBuf ) - 1 ) // in case of bad client 530 | { 531 | memset( RecvBuf, 0x00, sizeof( RecvBuf ) ); 532 | bufPos = 0; 533 | state = hdrStateUnknown; 534 | } 535 | 536 | // we always read 1 byte less than the buffer length, so all string ops here will not panic 537 | int res = socketread( m_RtspClient, RecvBuf + bufPos, sizeof( RecvBuf ) - bufPos - 1, readTimeoutMs ); 538 | if ( res > 0 ) 539 | { 540 | bufPos += res; 541 | RecvBuf[ bufPos ] = '\0'; 542 | 543 | if ( debug ) printf( "+ read %d bytes\n", res ); 544 | 545 | if ( state == hdrStateUnknown && bufPos >= 6 ) // we need at least 4-letter at the line start with optional heading CRLF 546 | { 547 | if( NULL != strstr( RecvBuf, "\r\n" ) ) // got a full line 548 | { 549 | char *s = RecvBuf; 550 | if ( *s == '\r' && *(s + 1) == '\n' ) // skip allowed empty line at front 551 | s += 2; 552 | 553 | newCommandInit(); 554 | // find out the command type 555 | m_RtspCmdType = RTSP_UNKNOWN; 556 | 557 | if ( strncmp( s, "OPTIONS ", 8 ) == 0 ) m_RtspCmdType = RTSP_OPTIONS; 558 | else if ( strncmp( s, "DESCRIBE ", 9 ) == 0 ) m_RtspCmdType = RTSP_DESCRIBE; 559 | else if ( strncmp( s, "SETUP ", 6 ) == 0 ) m_RtspCmdType = RTSP_SETUP; 560 | else if ( strncmp( s, "PLAY ", 5 ) == 0 ) m_RtspCmdType = RTSP_PLAY; 561 | else if ( strncmp( s, "TEARDOWN ", 9 ) == 0 ) m_RtspCmdType = RTSP_TEARDOWN; 562 | 563 | if( m_RtspCmdType != RTSP_UNKNOWN ) // got some 564 | state = hdrStateGotMethod; 565 | else 566 | state = hdrStateInvalid; 567 | } 568 | } // if state == hdrStateUnknown 569 | 570 | if ( state != hdrStateUnknown ) // in all cases we need to slurp the whole header before answering 571 | { 572 | // per https://tools.ietf.org/html/rfc2326 we need to look for an empty line 573 | // to be sure that we got the correctly formed header. Also starting CRLF should be ignored. 574 | char *s = strstr( bufPos > 4 ? RecvBuf + bufPos - 4 : RecvBuf, "\r\n\r\n" ); // try to save cycles by searching in the new data only 575 | 576 | if ( s == NULL ) // no end of header seen yet 577 | return true; 578 | 579 | if ( state == hdrStateInvalid ) // tossing some immediate answer, so client don't fall into endless stupor 580 | { 581 | // not sure which code is more appropriate and if CSeq is needed here? 582 | int l = snprintf( RecvBuf, sizeof(RecvBuf), "RTSP/1.0 400 Bad Request\r\nCSeq: %u\r\n\r\n", m_CSeq ); 583 | socketsend( m_RtspClient, RecvBuf, l ); 584 | bufPos = 0; 585 | return false; 586 | } 587 | } 588 | 589 | RTSP_CMD_TYPES C = Handle_RtspRequest( RecvBuf, res ); 590 | 591 | if ( C == RTSP_PLAY ) 592 | m_streaming = true; 593 | 594 | else if ( C == RTSP_TEARDOWN ) 595 | m_stopped = true; 596 | 597 | // cleaning up 598 | state = hdrStateUnknown; 599 | bufPos = 0; 600 | 601 | return true; 602 | } // res > 0 603 | else if ( res == 0 ) 604 | { 605 | printf("client closed socket, exiting\n"); 606 | m_stopped = true; 607 | return true; 608 | } 609 | else 610 | { 611 | // Timeout on read 612 | return false; 613 | } 614 | } 615 | --------------------------------------------------------------------------------