From 9e4c6e452f527f676fbac801d352e1c0945ba215 Mon Sep 17 00:00:00 2001 From: lxowalle Date: Wed, 15 May 2024 20:29:47 +0800 Subject: [PATCH] * add rtmp demo --- .../3rd_party/media_server/CMakeLists.txt | 88 +- components/3rd_party/media_server/Kconfig | 2 +- .../3rd_party/media_server/component.py | 3 + .../media_server/inc/h265-camera-source.h | 76 ++ .../3rd_party/media_server/inc/media-source.h | 30 + .../media_server/inc/rtp-tcp-transport.h | 36 + .../media_server/inc/rtp-udp-transport.h | 26 + .../media_server/inc/rtsp-server-internal.h | 49 ++ .../3rd_party/media_server/inc/rtsp_server.h | 23 + components/3rd_party/media_server/readme.md | 13 - .../media_server/src/h265-camera-source.cpp | 333 ++++++++ .../media_server/src/rtp-streaming-test.cpp | 274 ++++++ .../media_server/src/rtp-udp-transport.cpp | 50 ++ .../media_server/src/rtsp_server.cpp | 800 ++++++++++++++++++ components/vision/include/maix_rtmp.hpp | 137 +++ .../vision/port/linux/maix_rtmp_linux.cpp | 55 ++ .../vision/port/maixcam/maix_rtmp_maixcam.cpp | 261 ++++++ .../vision/port/maixcam/maix_rtsp_maixcam.cpp | 2 +- examples/rtmp_demo/.gitignore | 9 + examples/rtmp_demo/README.md | 5 + examples/rtmp_demo/app.yaml | 11 + examples/rtmp_demo/main/CMakeLists.txt | 74 ++ examples/rtmp_demo/main/Kconfig | 0 examples/rtmp_demo/main/include/main.h | 3 + examples/rtmp_demo/main/src/main.cpp | 86 ++ 25 files changed, 2396 insertions(+), 50 deletions(-) create mode 100644 components/3rd_party/media_server/inc/h265-camera-source.h create mode 100644 components/3rd_party/media_server/inc/media-source.h create mode 100644 components/3rd_party/media_server/inc/rtp-tcp-transport.h create mode 100644 components/3rd_party/media_server/inc/rtp-udp-transport.h create mode 100644 components/3rd_party/media_server/inc/rtsp-server-internal.h create mode 100644 components/3rd_party/media_server/inc/rtsp_server.h create mode 100644 components/3rd_party/media_server/src/h265-camera-source.cpp create mode 100644 components/3rd_party/media_server/src/rtp-streaming-test.cpp create mode 100644 components/3rd_party/media_server/src/rtp-udp-transport.cpp create mode 100644 components/3rd_party/media_server/src/rtsp_server.cpp create mode 100644 components/vision/include/maix_rtmp.hpp create mode 100644 components/vision/port/linux/maix_rtmp_linux.cpp create mode 100644 components/vision/port/maixcam/maix_rtmp_maixcam.cpp create mode 100644 examples/rtmp_demo/.gitignore create mode 100644 examples/rtmp_demo/README.md create mode 100644 examples/rtmp_demo/app.yaml create mode 100644 examples/rtmp_demo/main/CMakeLists.txt create mode 100644 examples/rtmp_demo/main/Kconfig create mode 100644 examples/rtmp_demo/main/include/main.h create mode 100644 examples/rtmp_demo/main/src/main.cpp diff --git a/components/3rd_party/media_server/CMakeLists.txt b/components/3rd_party/media_server/CMakeLists.txt index 71cca84d..bdd6cd89 100644 --- a/components/3rd_party/media_server/CMakeLists.txt +++ b/components/3rd_party/media_server/CMakeLists.txt @@ -3,35 +3,38 @@ set(media_server_unzip_path "${DL_EXTRACTED_PATH}/media_server") set(src_path "${media_server_unzip_path}/media_server-${media_server_version_str}") ############### Add include ################### -set(media_server_private_include_dir - "${src_path}/librtsp/source/server" - "${src_path}/sdk/include" - "${src_path}/sdk/libhttp/include" - "${src_path}/librtp/include" - "${src_path}/libmpeg/include" - "${src_path}/libflv/include" - "${src_path}/libmkv/include" - "${src_path}/avcodec/avcodec/include" - "${src_path}/avcodec/avbsf/include" - "${src_path}/libmov/include" - "${src_path}/include" - "${src_path}/media") -# list(APPEND ADD_PRIVATE_INCLUDE ${media_server_private_include_dir}) - -set(media_server_include_dir "${src_path}" - ".") - -list(APPEND ADD_INCLUDE ${media_server_include_dir} - ${media_server_private_include_dir}) - +set(media_server_private_include_dir "${src_path}/include/avcodec/avbsf/include" + "${src_path}/include/avcodec/avcodec/include" + "${src_path}/include/avcodec/h264/include" + "${src_path}/include/avcodec/h265/include" + + "${src_path}/include/sdk/include" + "${src_path}/include/sdk/libaio/include" + "${src_path}/include/sdk/libhttp/include" + "${src_path}/include/sdk/libice/include" + + "${src_path}/include/media-server/libdash/include" + "${src_path}/include/media-server/libice/include" + "${src_path}/include/media-server/libflv/include" + "${src_path}/include/media-server/libhls/include" + "${src_path}/include/media-server/libmkv/include" + "${src_path}/include/media-server/libmov/include" + "${src_path}/include/media-server/libmpeg/include" + "${src_path}/include/media-server/librtmp/include" + "${src_path}/include/media-server/librtp/include" + "${src_path}/include/media-server/librtsp/include" + "${src_path}/include/media-server/libsip/include") +set(media_server_include_dir "inc" "${src_path}") +list(APPEND ADD_INCLUDE ${media_server_include_dir} ${media_server_private_include_dir}) +set_property(SOURCE ${media_server_include_dir} PROPERTY GENERATED 1) +set_property(SOURCE ${media_server_private_include_dir} PROPERTY GENERATED 1) ############################################### ############ Add source files ################# -list(APPEND ADD_SRCS -) +aux_source_directory("src" ADD_SRCS) +# append_srcs_dir(ADD_SRCS ".") # append source file in src dir to var ADD_SRCS +# set_property(SOURCE ${ADD_SRCS} PROPERTY GENERATED 1) -append_srcs_dir(ADD_SRCS "${src_path}" - "${src_path}/media") # append source file in src dir to var ADD_SRCS # list(REMOVE_ITEM COMPONENT_SRCS "src/test2.c") # FILE(GLOB_RECURSE EXTRA_SRC "src/*.c") # FILE(GLOB EXTRA_SRC "src/*.c") @@ -63,16 +66,31 @@ append_srcs_dir(ADD_SRCS "${src_path}" ############################################### ############ Add static libs ################## -list(APPEND ADD_STATIC_LIB "${src_path}/libs/librtsp.a" - "${src_path}/libs/libhttp.a" - "${src_path}/libs/libflv.a" - "${src_path}/libs/libmov.a" - "${src_path}/libs/librtp.a" - "${src_path}/libs/libmpeg.a" - "${src_path}/libs/libsdk.a" - "${src_path}/libs/libavcodec.a" - "${src_path}/libs/libavbsf.a") +list(APPEND ADD_STATIC_LIB + + "${src_path}/lib/libdash.a" + "${src_path}/lib/libhls.a" + "${src_path}/lib/libmkv.a" + "${src_path}/lib/libmov.a" + "${src_path}/lib/libmpeg.a" + "${src_path}/lib/librtmp.a" + "${src_path}/lib/libflv.a" + "${src_path}/lib/librtp.a" + "${src_path}/lib/librtsp.a" + "${src_path}/lib/libsip.a" + + "${src_path}/lib/libavbsf.a" + "${src_path}/lib/libavcodec.a" + "${src_path}/lib/libh264.a" + "${src_path}/lib/libh265.a" + "${src_path}/lib/libhttp.a" + "${src_path}/lib/libice.a" + "${src_path}/lib/libsdk.a" + + +) +set_property(SOURCE ${ADD_STATIC_LIB} PROPERTY GENERATED 1) ############################################### ############ Add dynamic libs ################## @@ -87,7 +105,7 @@ list(APPEND ADD_STATIC_LIB "${src_path}/libs/librtsp.a" #### Add compile option for this component #### and components denpend on this component -list(APPEND ADD_DEFINITIONS -D__ERROR__=00*10000000+__LINE__*1000) +list(APPEND ADD_DEFINITIONS -D__ERROR__=00*10000000+__LINE__*1000 -DOS_LINUX) ############################################### diff --git a/components/3rd_party/media_server/Kconfig b/components/3rd_party/media_server/Kconfig index 743743f2..3ec14d91 100644 --- a/components/3rd_party/media_server/Kconfig +++ b/components/3rd_party/media_server/Kconfig @@ -14,7 +14,7 @@ config MEDIA_SERVER_VERSION_MINOR config MEDIA_SERVER_VERSION_PATCH int "media_server package patch version" - default 1 + default 2 help media_server package patch version endmenu diff --git a/components/3rd_party/media_server/component.py b/components/3rd_party/media_server/component.py index 204e0fd5..df8c85ca 100644 --- a/components/3rd_party/media_server/component.py +++ b/components/3rd_party/media_server/component.py @@ -11,6 +11,9 @@ def add_file_downloads(confs : dict) -> list: elif version == "1.0.1": url = "https://files.catbox.moe/acqhv1.zip" sha256sum = "ce06dc3d03b6036165956e60afd5eec5bfd37e7746e4b427f2099732478ecc22" + elif version == "1.0.2": + url = "https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/media_server-1.0.2.zip" + sha256sum = "b9872dbe52fae4d4b60a0db827533877b20186e9457525345407fdad8e187704" else: raise Exception(f"version {version} not support") filename = f"media_server-{version}.zip" diff --git a/components/3rd_party/media_server/inc/h265-camera-source.h b/components/3rd_party/media_server/inc/h265-camera-source.h new file mode 100644 index 00000000..2ec1209a --- /dev/null +++ b/components/3rd_party/media_server/inc/h265-camera-source.h @@ -0,0 +1,76 @@ +#ifndef _h265_camera_source_h_ +#define _h265_camera_source_h_ + +#include "media-source.h" +#include "sys/process.h" +#include "time64.h" +#include "rtp.h" +#include +#include "pthread.h" +#include + +class H265CameraSource : public IMediaSource +{ +public: + H265CameraSource(const char *file); + virtual ~H265CameraSource(); + +public: + virtual int Play(); + virtual int Pause(); + virtual int Seek(int64_t pos); + virtual int SetSpeed(double speed); + virtual int GetDuration(int64_t& duration) const; + virtual int GetSDPMedia(std::string& sdp) const; + virtual int GetRTPInfo(const char* uri, char *rtpinfo, size_t bytes) const; + virtual int SetTransport(const char* track, std::shared_ptr transport); + int SetNextFrame(const uint8_t* ptr, size_t bytes); + +private: + int GetNextFrame(int64_t &dts, const uint8_t* &ptr, size_t &bytes); + int FreeNextFrame(); + + static void OnRTCPEvent(void* param, const struct rtcp_msg_t* msg); + void OnRTCPEvent(const struct rtcp_msg_t* msg); + int SendRTCP(); + + static void* RTPAlloc(void* param, int bytes); + static void RTPFree(void* param, void *packet); + static int RTPPacket(void* param, const void *packet, int bytes, uint32_t timestamp, int flags); + +private: + void* m_rtp; + uint32_t m_timestamp; + time64_t m_rtp_clock; + time64_t m_rtcp_clock; + // H265CameraReader m_reader; + std::shared_ptr m_transport; + + struct vframe_t + { + const uint8_t* nalu; + int64_t time; + long bytes; + bool idr; // IDR frame + + bool operator < (const struct vframe_t &v) const + { + return time < v.time; + } + }; + typedef std::list lframes_t; + lframes_t m_videos_list; + lframes_t::iterator m_video; + pthread_mutex_t m_lock; + + std::list > m_sps; + + int m_status; + int64_t m_pos; + double m_speed; + + void *m_rtppacker; + unsigned char m_packet[MAX_UDP_PACKET+14]; +}; + +#endif /* !_h265_camera_source_h_ */ diff --git a/components/3rd_party/media_server/inc/media-source.h b/components/3rd_party/media_server/inc/media-source.h new file mode 100644 index 00000000..c63574b1 --- /dev/null +++ b/components/3rd_party/media_server/inc/media-source.h @@ -0,0 +1,30 @@ +#ifndef _media_source_h_ +#define _media_source_h_ + +#include +#include + +#ifndef MAX_UDP_PACKET +#define MAX_UDP_PACKET (1450-16) +#endif + +struct IRTPTransport +{ + virtual int Send(bool rtcp, const void* data, size_t bytes) = 0; +}; + +struct IMediaSource +{ + virtual ~IMediaSource(){} + + virtual int Play() = 0; + virtual int Pause() = 0; + virtual int Seek(int64_t pos) = 0; + virtual int SetSpeed(double speed) = 0; + virtual int GetDuration(int64_t& duration) const = 0; + virtual int GetSDPMedia(std::string& sdp) const = 0; + virtual int GetRTPInfo(const char* uri, char *rtpinfo, size_t bytes) const = 0; + virtual int SetTransport(const char* track, std::shared_ptr transport) = 0; +}; + +#endif /* !_media_source_h_ */ diff --git a/components/3rd_party/media_server/inc/rtp-tcp-transport.h b/components/3rd_party/media_server/inc/rtp-tcp-transport.h new file mode 100644 index 00000000..63cc764b --- /dev/null +++ b/components/3rd_party/media_server/inc/rtp-tcp-transport.h @@ -0,0 +1,36 @@ +#ifndef _rtp_tcp_transport_h_ +#define _rtp_tcp_transport_h_ + +#include "media-source.h" +#include "rtsp-server-internal.h" + +class RTPTcpTransport : public IRTPTransport +{ +public: + RTPTcpTransport(rtsp_server_t* rtsp, uint8_t rtp, uint8_t rtcp): m_rtp(rtp), m_rtcp(rtcp), m_rtsp(rtsp) {} + virtual ~RTPTcpTransport() {} + +public: + virtual int Send(bool rtcp, const void* data, size_t bytes) + { + assert(bytes < (1 << 16)); + if (bytes >= (1 << 16)) + return -E2BIG; + + m_packet[0] = '$'; + m_packet[1] = rtcp ? m_rtcp : m_rtp; + m_packet[2] = (bytes >> 8) & 0xFF; + m_packet[3] = bytes & 0xff; + memcpy(m_packet + 4, data, bytes); + int r = rtsp_server_send_interleaved_data(m_rtsp, m_packet, bytes + 4); + return 0 == r ? bytes : r; + } + +private: + uint8_t m_rtp; + uint8_t m_rtcp; + rtsp_server_t* m_rtsp; + uint8_t m_packet[4 + (1 << 16)]; +}; + +#endif /* !_rtp_tcp_transport_h_ */ diff --git a/components/3rd_party/media_server/inc/rtp-udp-transport.h b/components/3rd_party/media_server/inc/rtp-udp-transport.h new file mode 100644 index 00000000..7ced2de6 --- /dev/null +++ b/components/3rd_party/media_server/inc/rtp-udp-transport.h @@ -0,0 +1,26 @@ +#ifndef _rtp_udp_transport_h_ +#define _rtp_udp_transport_h_ + +#include "sys/sock.h" +#include "media-source.h" + +class RTPUdpTransport : public IRTPTransport +{ +public: + RTPUdpTransport(); + virtual ~RTPUdpTransport(); + +public: + virtual int Send(bool rtcp, const void* data, size_t bytes); + +public: + int Init(const char* ip, unsigned short port[2]); + int Init(socket_t socket[2], const char* peer, unsigned short port[2]); + +private: + socket_t m_socket[2]; + socklen_t m_addrlen[2]; + struct sockaddr_storage m_addr[2]; +}; + +#endif /* !_rtp_udp_transport_h_ */ diff --git a/components/3rd_party/media_server/inc/rtsp-server-internal.h b/components/3rd_party/media_server/inc/rtsp-server-internal.h new file mode 100644 index 00000000..146909b1 --- /dev/null +++ b/components/3rd_party/media_server/inc/rtsp-server-internal.h @@ -0,0 +1,49 @@ +#ifndef _rtsp_server_internal_h_ +#define _rtsp_server_internal_h_ + +#include "rtsp-server.h" +#include "http-parser.h" +#include "rtsp-header-session.h" +#include +#include +#include +#include + +#if defined(OS_WINDOWS) +#define strcasecmp _stricmp +#endif + +#define MAX_UDP_PACKAGE 1024 + +#define USER_AGENT "ireader/media-server" + +struct rtsp_server_t +{ + struct rtsp_handler_t handler; + void *param, *sendparam; + + http_parser_t* parser; + struct rtsp_header_session_t session; + + unsigned int cseq; + char reply[MAX_UDP_PACKAGE]; + + char ip[65]; // IPv4/IPv6 + unsigned short port; +}; + +int rtsp_server_handle(struct rtsp_server_t *rtsp); +int rtsp_server_options(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_announce(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_describe(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_setup(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_play(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_pause(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_teardown(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_get_parameter(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_set_parameter(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_record(struct rtsp_server_t *rtsp, const char* uri); +int rtsp_server_reply(struct rtsp_server_t *rtsp, int code); +int rtsp_server_reply2(struct rtsp_server_t *rtsp, int code, const char* header, const void* data, int bytes); + +#endif /* !_rtsp_server_internal_h_ */ diff --git a/components/3rd_party/media_server/inc/rtsp_server.h b/components/3rd_party/media_server/inc/rtsp_server.h new file mode 100644 index 00000000..34d3e17f --- /dev/null +++ b/components/3rd_party/media_server/inc/rtsp_server.h @@ -0,0 +1,23 @@ +#ifndef __RTSP_SERVER_H +#define __RTSP_SERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdlib.h" +#include "stdint.h" + +int rtsp_server_init(char *ip, int port); +int rtsp_server_deinit(void); +char *rtsp_get_server_ip(void); +int rtsp_get_server_port(void); +int rtsp_server_start(void); +int rtsp_server_stop(void); +void rtsp_send_h265_data(uint8_t *asddata, size_t data_len); + +#ifdef __cplusplus +} +#endif + +#endif // __RTSP_SERVER_H diff --git a/components/3rd_party/media_server/readme.md b/components/3rd_party/media_server/readme.md index a13774f7..89931ee4 100644 --- a/components/3rd_party/media_server/readme.md +++ b/components/3rd_party/media_server/readme.md @@ -1,14 +1 @@ # readme - -对openmv库进行了代码裁剪,保证代码量尽量的小,以方便在出现问题时可以快速定位到是否是移植带来的问题。 - -其中主要修改的部分: -1. alloc文件用于内存管理,由于openmv的内存管理与板级强相关,所以这里的实现与源码有差异 -2. common文件用于存放一些通用函数。这里删除掉了大部分用不到的文件,只保留了一些方便imlib编译通过的文件。删除大部分文件的优势是可以更快的理解imlib依赖的代码,缺点是如果源码库中common文件有更新,需要手动同步到这里 -3. imlib文件用于存放imlib的核心代码 -4. ports文件保存了配置文件、与板级相关的代码、与第三方库相关的代码 - - -# TODO: - -1. 未实现文件操作功能。在ports/linux中添加了oofatfs只是为了编译通过,实际上并没有实现文件操作功能 \ No newline at end of file diff --git a/components/3rd_party/media_server/src/h265-camera-source.cpp b/components/3rd_party/media_server/src/h265-camera-source.cpp new file mode 100644 index 00000000..1df6f921 --- /dev/null +++ b/components/3rd_party/media_server/src/h265-camera-source.cpp @@ -0,0 +1,333 @@ +#include "h265-camera-source.h" +#include "cstringext.h" +#include "base64.h" +#include "rtp-profile.h" +#include "rtp-payload.h" +#include + +#define FRAME_TIME_MS (33) +#define H265_NAL(v) ((v>> 1) & 0x3f) + +enum { NAL_IDR_W_RADL = 19, NAL_IDR_N_LP= 20, NAL_VPS = 32, NAL_SPS = 33, NAL_PPS = 34, NAL_SEI = 39}; + +extern "C" uint32_t rtp_ssrc(void); + +H265CameraSource::H265CameraSource(const char *file) +{ + m_speed = 1.0; + m_status = 0; + m_rtp_clock = 0; + m_rtcp_clock = 0; + m_timestamp = 0; + (void)file; + uint32_t ssrc = rtp_ssrc(); + static struct rtp_payload_t s_rtpfunc = { + H265CameraSource::RTPAlloc, + H265CameraSource::RTPFree, + H265CameraSource::RTPPacket, + }; + m_rtppacker = rtp_payload_encode_create(RTP_PAYLOAD_H265, "H265", (uint16_t)ssrc, ssrc, &s_rtpfunc, this); + + struct rtp_event_t event; + event.on_rtcp = OnRTCPEvent; + m_rtp = rtp_create(&event, this, ssrc, m_timestamp, 90000, 4*1024, 1); + rtp_set_info(m_rtp, "RTSPServer", "szj.h265"); + + pthread_mutex_init(&m_lock, NULL); + pthread_mutex_unlock(&m_lock); +} + +H265CameraSource::~H265CameraSource() +{ + if(m_rtp) + rtp_destroy(m_rtp); + + if(m_rtppacker) + rtp_payload_encode_destroy(m_rtppacker); + + std::list>::iterator sps_iter; + for (sps_iter = m_sps.begin(); sps_iter != m_sps.end(); sps_iter ++) { + if (sps_iter->first) { + free((void *)sps_iter->first); + } + } + + pthread_mutex_destroy(&m_lock); +} + +int H265CameraSource::SetTransport(const char* /*track*/, std::shared_ptr transport) +{ + m_transport = transport; + return 0; +} + +int H265CameraSource::Play() +{ + m_status = 1; +// + //uint32_t timestamp = 0; + time64_t clock = time64_now(); + if (0 == m_rtp_clock) + m_rtp_clock = clock; + + if(m_rtp_clock + FRAME_TIME_MS < clock) + { + size_t bytes; + const uint8_t* ptr; + + pthread_mutex_lock(&m_lock); + if(0 == GetNextFrame(m_pos, ptr, bytes)) + { + // for(int i=0;i pr; + pr.second = bytes; + pr.first = (const uint8_t*)malloc(bytes); + assert(pr.first); + memcpy((uint8_t *)pr.first, p, bytes); + m_sps.push_back(pr); + } else { + pthread_mutex_unlock(&m_lock); + return 0; + } + } + + vframe_t frame; + vframe_t last_frame = m_videos_list.back(); + frame.bytes = bytes; + frame.idr = (NAL_IDR_N_LP == nal_unit_type || NAL_IDR_W_RADL == nal_unit_type); // IDR-frame; + frame.time = FRAME_TIME_MS + last_frame.time; + frame.nalu = (const uint8_t*)malloc(bytes); + assert(frame.nalu); + memcpy((void *)frame.nalu, data, bytes); + m_videos_list.push_back(frame); + + pthread_mutex_unlock(&m_lock); + return 0; +} + +int H265CameraSource::GetNextFrame(int64_t &dts, const uint8_t* &ptr, size_t &bytes) +{ + lframes_t::iterator frame = m_videos_list.begin(); + if (frame == m_videos_list.end()) { + return -1; + } + + ptr = frame->nalu; + dts = frame->time; + bytes = frame->bytes; + + return 0; +} + +int H265CameraSource::FreeNextFrame() +{ + lframes_t::iterator frame = m_videos_list.begin(); + if (frame != m_videos_list.end()) { + free((void *)frame->nalu); + m_videos_list.pop_front(); + } + // printf("FreeNextFrame remain size:%ld\n", m_videos_list.size()); + return 0; +} + + +int H265CameraSource::Pause() +{ + m_status = 2; + m_rtp_clock = 0; + return 0; +} + +int H265CameraSource::Seek(int64_t pos) +{ + m_pos = pos; + m_rtp_clock = 0; + return 0; +} + +int H265CameraSource::SetSpeed(double speed) +{ + m_speed = speed; + return 0; +} + +int H265CameraSource::GetDuration(int64_t& duration) const +{ + (void)duration; + return 0; +} + +int H265CameraSource::GetSDPMedia(std::string& sdp) const +{ + static const char* pattern = + "m=video 0 RTP/AVP %d\n" + "a=rtpmap:%d H265/90000\n" + "a=fmtp:%d profile-level-id=%02X%02X%02X;" + "packetization-mode=1;" + "sprop-parameter-sets="; + + char base64[512] = {0}; + std::string parameters; + + const std::list >& sps = m_sps; + std::list >::const_iterator it; + for(it = sps.begin(); it != sps.end(); ++it) + { + if(parameters.empty()) + { + snprintf(base64, sizeof(base64), pattern, + RTP_PAYLOAD_H265, RTP_PAYLOAD_H265,RTP_PAYLOAD_H265, + (unsigned int)(it->first[1]), (unsigned int)(it->first[2]), (unsigned int)(it->first[3])); + sdp = base64; + } + else + { + parameters += ','; + } + + size_t bytes = it->second; + assert((bytes+2)/3*4 + bytes/57 + 1 < sizeof(base64)); + bytes = base64_encode(base64, it->first, bytes); + base64[bytes] = '\0'; + assert(strlen(base64) > 0); + parameters += base64; + } + + sdp += parameters; + sdp += '\n'; + return sps.empty() ? -1 : 0; +} + +int H265CameraSource::GetRTPInfo(const char* uri, char *rtpinfo, size_t bytes) const +{ + uint16_t seq; + uint32_t timestamp; + rtp_payload_encode_getinfo(m_rtppacker, &seq, ×tamp); + + // url=rtsp://video.example.com/twister/video;seq=12312232;rtptime=78712811 + snprintf(rtpinfo, bytes, "url=%s;seq=%hu;rtptime=%u", uri, seq, timestamp); + return 0; +} + +void H265CameraSource::OnRTCPEvent(const struct rtcp_msg_t* msg) +{ + (void)msg; +} + +void H265CameraSource::OnRTCPEvent(void* param, const struct rtcp_msg_t* msg) +{ + H265CameraSource *self = (H265CameraSource *)param; + self->OnRTCPEvent(msg); +} + +int H265CameraSource::SendRTCP() +{ + // make sure have sent RTP packet + + time64_t clock = time64_now(); + int interval = rtp_rtcp_interval(m_rtp); + if(0 == m_rtcp_clock || m_rtcp_clock + interval < clock) + { + char rtcp[1024] = {0}; + size_t n = rtp_rtcp_report(m_rtp, rtcp, sizeof(rtcp)); + + // send RTCP packet + m_transport->Send(true, rtcp, n); + + m_rtcp_clock = clock; + } + + return 0; +} + +void* H265CameraSource::RTPAlloc(void* param, int bytes) +{ + H265CameraSource *self = (H265CameraSource*)param; + assert(bytes <= (int)sizeof(self->m_packet)); + return self->m_packet; +} + +void H265CameraSource::RTPFree(void* param, void *packet) +{ + H265CameraSource *self = (H265CameraSource*)param; + assert(self->m_packet == packet); +} + +int H265CameraSource::RTPPacket(void* param, const void *packet, int bytes, uint32_t /*timestamp*/, int /*flags*/) +{ + H265CameraSource *self = (H265CameraSource*)param; + assert(self->m_packet == packet); + // const char* ptr = (const char*)packet; + // for(int i=0;i 4){ + // exit(-1); + // } + int r = self->m_transport->Send(false, packet, bytes); + if (r != bytes) + return -1; + + return rtp_onsend(self->m_rtp, packet, bytes/*, time*/); +} diff --git a/components/3rd_party/media_server/src/rtp-streaming-test.cpp b/components/3rd_party/media_server/src/rtp-streaming-test.cpp new file mode 100644 index 00000000..e9dfa3c3 --- /dev/null +++ b/components/3rd_party/media_server/src/rtp-streaming-test.cpp @@ -0,0 +1,274 @@ +#include "mov-reader.h" +#include "mov-format.h" +#include "mpeg4-hevc.h" +#include "mpeg4-avc.h" +#include "mpeg4-aac.h" +#include "webm-vpx.h" +#include "aom-av1.h" +#include "rtp-profile.h" +#include "rtsp-muxer.h" +#include "sockutil.h" +#include "sys/system.h" +#include +#include +#include +#include +#include + +#define IP "127.0.0.1" + +#define SOCKET_STORAGE_TO_ADDR(storage) (const struct sockaddr*)(storage), socket_addr_len((const struct sockaddr*)(storage)) + +extern "C" const struct mov_buffer_t* mov_file_buffer(void); + +static uint8_t s_packet[2 * 1024 * 1024]; +static uint8_t s_buffer[4 * 1024 * 1024]; +static struct mpeg4_hevc_t s_hevc; +static struct mpeg4_avc_t s_avc; +static struct mpeg4_aac_t s_aac; +static struct webm_vpx_t s_vpx; +static struct aom_av1_t s_av1; + +struct rtp_streaming_test_t; +struct rtp_streaming_test_stream_t +{ + struct rtp_streaming_test_t* ctx; + + int av; + int object; + int track; + int psi; + int64_t dts; + + int mid; + struct rtsp_muxer_t* rtp; + + socket_t udp[2]; + struct sockaddr_storage addr[2]; +}; + +struct rtp_streaming_test_t +{ + struct rtp_streaming_test_stream_t a, v; + uint64_t clock; +}; + +static int rtp_encode_packet(void* param, int pid, const void* packet, int bytes, uint32_t timestamp, int /*flags*/) +{ + static uint8_t rtcp[1500]; + struct rtp_streaming_test_stream_t* ctx = (struct rtp_streaming_test_stream_t*)param; + assert(bytes == socket_sendto(ctx->udp[0], packet, bytes, 0, SOCKET_STORAGE_TO_ADDR(&ctx->addr[0]))); + + int r = rtsp_muxer_rtcp(ctx->rtp, ctx->mid, rtcp, sizeof(rtcp)); + if (r > 0) + { + assert(r == socket_sendto(ctx->udp[1], rtcp, r, 0, SOCKET_STORAGE_TO_ADDR(&ctx->addr[1]))); + } + + return 0; +} + +static inline const char* ftimestamp(int64_t timestamp, char* buf) +{ + uint32_t t = (uint32_t)timestamp; + sprintf(buf, "%02u:%02u:%02u.%03u", t / 3600000, (t / 60000) % 60, (t / 1000) % 60, t % 1000); + return buf; +} + +static void onread(void* param, uint32_t track, const void* buffer, size_t bytes, int64_t pts, int64_t dts, int flags) +{ + static char s_pts[64], s_dts[64]; + static int64_t v_pts, v_dts; + static int64_t a_pts, a_dts; + struct rtp_streaming_test_t* ctx = (struct rtp_streaming_test_t*)param; + + uint64_t clock = system_clock(); + if (clock - ctx->clock + 5 < dts) + system_sleep(dts - (clock - ctx->clock + 5)); + + if (ctx->v.track == track) + { + if (MOV_OBJECT_H264 == ctx->v.object) + { + bytes = h264_mp4toannexb(&s_avc, buffer, bytes, s_packet, sizeof(s_packet)); + buffer = s_packet; + } + else if (MOV_OBJECT_HEVC == ctx->v.object) + { + bytes = h265_mp4toannexb(&s_hevc, buffer, bytes, s_packet, sizeof(s_packet)); + buffer = s_packet; + } + else if (MOV_OBJECT_AV1 == ctx->v.object) + { + //n = aom_av1_codec_configuration_record_save(&s_av1, s_packet, sizeof(s_packet)); + } + else if (MOV_OBJECT_VP9 == ctx->v.object || MOV_OBJECT_VP8 == ctx->v.object) + { + //n = aom_av1_codec_configuration_record_save(&s_av1, s_packet, sizeof(s_packet)); + } + else + { + assert(0); + } + + printf("[V] pts: %s, dts: %s, diff: %03d/%03d, %d%s\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - v_pts), (int)(dts - v_dts), (int)bytes, flags ? " [I]" : ""); + v_pts = pts; + v_dts = dts; + assert(0 == rtsp_muxer_input(ctx->v.rtp, ctx->v.mid, pts, dts, buffer, bytes, 0)); + } + else if (ctx->a.track == track) + { + if (MOV_OBJECT_AAC == ctx->a.object) + { + bytes = mpeg4_aac_adts_save(&s_aac, bytes, s_packet, sizeof(s_packet)); + buffer = s_packet; + } + else if (MOV_OBJECT_OPUS == ctx->a.object) + { + } + else + { + assert(0); + } + + printf("[A] pts: %s, dts: %s, diff: %03d/%03d, %d\n", ftimestamp(pts, s_pts), ftimestamp(dts, s_dts), (int)(pts - a_pts), (int)(dts - a_dts), (int)bytes); + a_pts = pts; + a_dts = dts; + assert(0 == rtsp_muxer_input(ctx->a.rtp, ctx->a.mid, pts, dts, buffer, bytes, 0)); + } + else + { + assert(0); + } +} + +static void mov_video_info(void* param, uint32_t track, uint8_t object, int /*width*/, int /*height*/, const void* extra, size_t bytes) +{ + struct rtp_streaming_test_t* ctx = (struct rtp_streaming_test_t*)param; + ctx->v.track = track; + ctx->v.object = object; + ctx->v.av = 1; + ctx->v.udp[0] = socket_udp_bind_ipv4(NULL, 0); + ctx->v.udp[1] = socket_udp_bind_ipv4(NULL, 0); + assert(0 == socket_addr_from(&ctx->v.addr[0], NULL, IP, 8004)); + assert(0 == socket_addr_from(&ctx->v.addr[1], NULL, IP, 8005)); + ctx->v.rtp = rtsp_muxer_create(rtp_encode_packet, &ctx->v); + + if (MOV_OBJECT_H264 == object) + { + assert(bytes == mpeg4_avc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s_avc)); + int pid = rtsp_muxer_add_payload(ctx->v.rtp, "RTP/AVP", 90000, 126, "H264", 0, 0, 0, extra, bytes); + ctx->v.mid = rtsp_muxer_add_media(ctx->v.rtp, pid, RTP_PAYLOAD_H264, extra, bytes); + } + else if (MOV_OBJECT_HEVC == object) + { + assert(bytes == mpeg4_hevc_decoder_configuration_record_load((const uint8_t*)extra, bytes, &s_hevc)); + int pid = rtsp_muxer_add_payload(ctx->v.rtp, "RTP/AVP", 90000, RTP_PAYLOAD_H265, "H265", 0, 0, 0, extra, bytes); + ctx->v.mid = rtsp_muxer_add_media(ctx->v.rtp, pid, RTP_PAYLOAD_H265, extra, bytes); + } + else if (MOV_OBJECT_AV1 == object) + { + assert(bytes == aom_av1_codec_configuration_record_load((const uint8_t*)extra, bytes, &s_av1)); + int pid = rtsp_muxer_add_payload(ctx->v.rtp, "RTP/AVP", 90000, RTP_PAYLOAD_AV1X, "AV1X", 0, 0, 0, extra, bytes); + ctx->v.mid = rtsp_muxer_add_media(ctx->v.rtp, pid, RTP_PAYLOAD_AV1X, extra, bytes); + } + else if (MOV_OBJECT_VP9 == object) + { + assert(bytes == webm_vpx_codec_configuration_record_load((const uint8_t*)extra, bytes, &s_vpx)); + int pid = rtsp_muxer_add_payload(ctx->v.rtp, "RTP/AVP", 90000, RTP_PAYLOAD_VP9, "VP9", 0, 0, 0, extra, bytes); + ctx->v.mid = rtsp_muxer_add_media(ctx->v.rtp, pid, RTP_PAYLOAD_VP9, extra, bytes); + } + else if (MOV_OBJECT_VP8 == object) + { + assert(bytes == webm_vpx_codec_configuration_record_load((const uint8_t*)extra, bytes, &s_vpx)); + int pid = rtsp_muxer_add_payload(ctx->v.rtp, "RTP/AVP", 90000, 100, "VP8", 0, 0, 0, extra, bytes); + ctx->v.mid = rtsp_muxer_add_media(ctx->v.rtp, pid, RTP_PAYLOAD_VP8, extra, bytes); + } + else + { + assert(0); + } +} + +static void mov_audio_info(void* param, uint32_t track, uint8_t object, int /*channel_count*/, int /*bit_per_sample*/, int sample_rate, const void* extra, size_t bytes) +{ + struct rtp_streaming_test_t* ctx = (struct rtp_streaming_test_t*)param; + ctx->a.track = track; + ctx->a.object = object; + ctx->a.av = 0; + ctx->a.udp[0] = socket_udp_bind_ipv4(NULL, 0); + ctx->a.udp[1] = socket_udp_bind_ipv4(NULL, 0); + assert(0 == socket_addr_from(&ctx->a.addr[0], NULL, IP, 5002)); + assert(0 == socket_addr_from(&ctx->a.addr[1], NULL, IP, 5003)); + ctx->a.rtp = rtsp_muxer_create(rtp_encode_packet, &ctx->a); + + if (MOV_OBJECT_AAC == object) + { + assert(bytes == mpeg4_aac_audio_specific_config_load((const uint8_t*)extra, bytes, &s_aac)); + int pid = rtsp_muxer_add_payload(ctx->a.rtp, "RTP/AVP", sample_rate, RTP_PAYLOAD_LATM, "MP4A-LATM", 0, 0, 0, extra, bytes); + ctx->a.mid = rtsp_muxer_add_media(ctx->a.rtp, pid, RTP_PAYLOAD_LATM, extra, bytes); + } + else if (MOV_OBJECT_OPUS == object) + { + assert(48000 == sample_rate); + int pid = rtsp_muxer_add_payload(ctx->a.rtp, "RTP/AVP", sample_rate, 111, "OPUS", 0, 0, 0, extra, bytes); + ctx->a.mid = rtsp_muxer_add_media(ctx->a.rtp, pid, RTP_PAYLOAD_OPUS, extra, bytes); + } + else + { + assert(0); + } +} + +void rtp_streaming_test(const char* mp4) +{ + struct rtp_streaming_test_t ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.a.ctx = &ctx; + ctx.v.ctx = &ctx; + + FILE* fp = fopen(mp4, "rb"); + mov_reader_t* mov = mov_reader_create(mov_file_buffer(), fp); + uint64_t duration = mov_reader_getduration(mov); + + struct mov_reader_trackinfo_t info = { mov_video_info, mov_audio_info }; + mov_reader_getinfo(mov, &info, &ctx); + + ctx.clock = system_clock(); + while (mov_reader_read(mov, s_buffer, sizeof(s_buffer), onread, &ctx) > 0) + { + int n = 0; + socket_t udp[4]; + if (ctx.v.udp[0] && socket_invalid != ctx.v.udp[0]) + { + udp[n] = ctx.v.udp[0]; + udp[n++] = ctx.v.udp[1]; + } + + if (ctx.a.udp[0] && socket_invalid != ctx.a.udp[0]) + { + udp[n] = ctx.a.udp[0]; + udp[n++] = ctx.a.udp[1]; + } + + socklen_t addrlen; + struct sockaddr_storage addr; + int64_t flags = socket_poll_readv(0, n, udp); + for (int i = 0; i < 4; i++) + { + // discard rtcp + if (flags & (1LL << i)) + { + socket_recvfrom(udp[i], s_buffer, sizeof(s_buffer), 0, (struct sockaddr*)&addr, &addrlen); + } + } + } + + if (ctx.a.rtp) + rtsp_muxer_destroy(ctx.a.rtp); + if (ctx.v.rtp) + rtsp_muxer_destroy(ctx.v.rtp); + + mov_reader_destroy(mov); + fclose(fp); +} diff --git a/components/3rd_party/media_server/src/rtp-udp-transport.cpp b/components/3rd_party/media_server/src/rtp-udp-transport.cpp new file mode 100644 index 00000000..0e406a3e --- /dev/null +++ b/components/3rd_party/media_server/src/rtp-udp-transport.cpp @@ -0,0 +1,50 @@ +#include "rtp-udp-transport.h" +#include "sockpair.h" +#include "ctypedef.h" +#include "port/ip-route.h" + +RTPUdpTransport::RTPUdpTransport() +{ + m_socket[0] = socket_invalid; + m_socket[1] = socket_invalid; +} + +RTPUdpTransport::~RTPUdpTransport() +{ + for (int i = 0; i < 2; i++) + { + if (socket_invalid != m_socket[i]) + socket_close(m_socket[i]); + m_socket[i] = socket_invalid; + } +} + +int RTPUdpTransport::Init(const char* ip, unsigned short port[2]) +{ + char local[SOCKET_ADDRLEN]; + int r1 = socket_addr_from(&m_addr[0], &m_addrlen[0], ip, port[0]); + int r2 = socket_addr_from(&m_addr[1], &m_addrlen[1], ip, port[1]); + if (0 != r1 || 0 != r2) + return 0 != r1 ? r1 : r2; + + r1 = ip_route_get(ip, local); + return sockpair_create(0==r1 ? local : NULL, m_socket, port); +} + +int RTPUdpTransport::Init(socket_t socket[2], const char* peer, unsigned short port[2]) +{ + int r1 = socket_addr_from(&m_addr[0], &m_addrlen[0], peer, port[0]); + int r2 = socket_addr_from(&m_addr[1], &m_addrlen[1], peer, port[1]); + if (0 != r1 || 0 != r2) + return 0 != r1 ? r1 : r2; + + m_socket[0] = socket[0]; + m_socket[1] = socket[1]; + return 0; +} + +int RTPUdpTransport::Send(bool rtcp, const void* data, size_t bytes) +{ + int i = rtcp ? 1 : 0; + return socket_sendto(m_socket[i], data, bytes, 0, (sockaddr*)&m_addr[i], m_addrlen[i]); +} diff --git a/components/3rd_party/media_server/src/rtsp_server.cpp b/components/3rd_party/media_server/src/rtsp_server.cpp new file mode 100644 index 00000000..49652007 --- /dev/null +++ b/components/3rd_party/media_server/src/rtsp_server.cpp @@ -0,0 +1,800 @@ +#include "cstringext.h" +#include "sys/sock.h" +#include "sys/thread.h" +#include "sys/system.h" +#include "sys/path.h" +#include "sys/sync.hpp" +#include "sockutil.h" +// #include "aio-worker.h" +#include "ctypedef.h" +#include "ntp-time.h" +#include "rtp-profile.h" +#include "rtsp-server.h" +// #include "media/ps-file-source.h" +// #include "media/h264-file-source.h" +// #include "media/h265-file-source.h" +#include "h265-camera-source.h" +// #include "media/mp4-file-source.h" +#include "rtp-udp-transport.h" +#include "rtp-tcp-transport.h" +#include "rtsp-server-aio.h" +#include "uri-parse.h" +#include "urlcodec.h" +#include "path.h" +#include +#include +#include "cpm/shared_ptr.h" + +#if defined(_HAVE_FFMPEG_) +#include "media/ffmpeg-file-source.h" +#include "media/ffmpeg-live-source.h" +#endif + +#define UDP_MULTICAST_ADDR "239.0.0.2" +#define UDP_MULTICAST_PORT 6000 + +// ffplay rtsp://127.0.0.1/vod/video/abc.mp4 +// Windows --> d:\video\abc.mp4 +// Linux --> ./video/abc.mp4 + +#if defined(OS_WINDOWS) +static const char* s_workdir = "d:\\"; +#else +static const char* s_workdir = "./"; +#endif + +static ThreadLocker s_locker; +static H265CameraSource *camera_source = NULL; +struct rtsp_media_t +{ + std::shared_ptr media; + std::shared_ptr transport; + uint8_t channel; // rtp over rtsp interleaved channel + int status; // setup-init, 1-play, 2-pause + rtsp_server_t* rtsp; +}; +typedef std::map TSessions; +static TSessions s_sessions; + +struct TFileDescription +{ + int64_t duration; + std::string sdpmedia; +}; +static std::map s_describes; + +static int rtsp_uri_parse(const char* uri, std::string& path) +{ + char path1[256]; + struct uri_t* r = uri_parse(uri, strlen(uri)); + if(!r) + return -1; + + url_decode(r->path, strlen(r->path), path1, sizeof(path1)); + path = path1; + uri_free(r); + return 0; +} + +static int rtsp_ondescribe(void* /*ptr*/, rtsp_server_t* rtsp, const char* uri) +{ + static const char* pattern_vod = + "v=0\n" + "o=- %llu %llu IN IP4 %s\n" + "s=%s\n" + "c=IN IP4 0.0.0.0\n" + "t=0 0\n" + "a=range:npt=0-%.1f\n" + "a=recvonly\n" + "a=control:*\n"; // aggregate control + + static const char* pattern_live = + "v=0\n" + "o=- %llu %llu IN IP4 %s\n" + "s=%s\n" + "c=IN IP4 0.0.0.0\n" + "t=0 0\n" + "a=range:npt=now-\n" // live + "a=recvonly\n" + "a=control:*\n"; // aggregate control + + std::string filename; + std::map::const_iterator it; + + rtsp_uri_parse(uri, filename); + if (strstartswith(filename.c_str(), "/live")) + { + filename = filename.c_str() + 5; + } + else if (strstartswith(filename.c_str(), "/live/")) + { + filename = filename.c_str() + 6; + } + else if (strstartswith(filename.c_str(), "/vod/")) + { + filename = path::join(s_workdir, filename.c_str() + 5); + } + else + { + assert(0); + return -1; + } + + char buffer[1024] = { 0 }; + { + AutoThreadLocker locker(s_locker); + it = s_describes.find(filename); + if(it == s_describes.end()) + { + // unlock + TFileDescription describe; + std::shared_ptr source; + if (0 == strcmp(filename.c_str(), "") || 0 == strcmp(filename.c_str(), "camera")) + { +#if defined(_HAVE_FFMPEG_) +#error "not support ffmpeg now!" + source.reset(new FFLiveSource("video=Integrated Webcam")); +#endif + camera_source = new H265CameraSource(filename.c_str()); + source.reset(camera_source); + source->GetDuration(describe.duration); + + int offset = snprintf(buffer, sizeof(buffer), pattern_live, ntp64_now(), ntp64_now(), "0.0.0.0", uri); + assert(offset > 0 && offset + 1 < (int)sizeof(buffer)); + } + else + { +// if (strendswith(filename.c_str(), ".ps")) +// source.reset(new PSFileSource(filename.c_str())); +// else if (strendswith(filename.c_str(), ".h264")) +// source.reset(new H264FileSource(filename.c_str())); +// else if (strendswith(filename.c_str(), ".h265")) +// source.reset(new H265FileSource(filename.c_str())); +// else +// { +// #if defined(_HAVE_FFMPEG_) +// source.reset(new FFFileSource(filename.c_str())); +// #else +// source.reset(new MP4FileSource(filename.c_str())); +// #endif +// } + // source->GetDuration(describe.duration); + + int offset = snprintf(buffer, sizeof(buffer), pattern_vod, ntp64_now(), ntp64_now(), "0.0.0.0", uri, describe.duration / 1000.0); + assert(offset > 0 && offset + 1 < (int)sizeof(buffer)); + } + + source->GetSDPMedia(describe.sdpmedia); + + // re-lock + it = s_describes.insert(std::make_pair(filename, describe)).first; + } + } + + std::string sdp = buffer; + sdp += it->second.sdpmedia; + // printf("sdp(%d):\n%s\n", strlen(sdp.c_str()), sdp.c_str()); + + const char *new_sdp = "v=0\n" +"o=- 16840380621439999083 16840380621439999083 IN IP4 0.0.0.0\n" +"s=rtsp://0.0.0.0:8554/live/camera\n" +"c=IN IP4 0.0.0.0\n" +"t=0 0\n" +"a=range:npt=0\n" +"a=recvonly\n" +"a=control:*\n" +"m=video 0 RTP/AVP 100\n" +"a=rtpmap:100 H265/90000\n" +"a=fmtp:100 profile-level-id=010C01;packetization-mode=1;sprop-parameter-sets=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJAAAAAQ=="; + + // printf("new sdp(%d):\n%s\n", strlen(new_sdp), new_sdp); + // return rtsp_server_reply_describe(rtsp, 200, sdp.c_str()); + return rtsp_server_reply_describe(rtsp, 200, new_sdp); +} + +static int rtsp_onsetup(void* /*ptr*/, rtsp_server_t* rtsp, const char* uri, const char* session, const struct rtsp_header_transport_t transports[], size_t num) +{ + std::string filename; + char rtsp_transport[128]; + const struct rtsp_header_transport_t *transport = NULL; + + rtsp_uri_parse(uri, filename); + if (strstartswith(filename.c_str(), "/live/")) + { + filename = filename.c_str() + 6; + } + else if (strstartswith(filename.c_str(), "/vod/")) + { + filename = path::join(s_workdir, filename.c_str() + 5); + } + else + { + assert(0); + return -1; + } + + if ('\\' == *filename.rbegin() || '/' == *filename.rbegin()) + filename.erase(filename.end() - 1); + + // const char* basename = path_basename(filename.c_str()); + // if (NULL == strchr(basename, '.')) // filter track1 + // filename.erase(basename - filename.c_str() - 1, std::string::npos); + + TSessions::iterator it; + + if(session) + { + AutoThreadLocker locker(s_locker); + it = s_sessions.find(session); + if(it == s_sessions.end()) + { + // 454 Session Not Found + return rtsp_server_reply_setup(rtsp, 454, NULL, NULL); + } + else + { + // don't support aggregate control + if (0) + { + // 459 Aggregate Operation Not Allowed + return rtsp_server_reply_setup(rtsp, 459, NULL, NULL); + } + } + } + else + { + rtsp_media_t item; + item.rtsp = rtsp; + item.channel = 0; + item.status = 0; + + if (0 == strcmp(filename.c_str(), "") || 0 == strcmp(filename.c_str(), "camera")) + { + camera_source = new H265CameraSource(filename.c_str()); + item.media.reset(camera_source); + } + else + { +// if (strendswith(filename.c_str(), ".ps")) +// item.media.reset(new PSFileSource(filename.c_str())); +// else if (strendswith(filename.c_str(), ".h264")) +// item.media.reset(new H264FileSource(filename.c_str())); +// else if (strendswith(filename.c_str(), ".h265")) +// item.media.reset(new H265FileSource(filename.c_str())); +// else +// { +// #if defined(_HAVE_FFMPEG_) +// item.media.reset(new FFFileSource(filename.c_str())); +// #else +// item.media.reset(new MP4FileSource(filename.c_str())); +// #endif +// } + } + + char rtspsession[32]; + snprintf(rtspsession, sizeof(rtspsession), "%p", item.media.get()); + + AutoThreadLocker locker(s_locker); + it = s_sessions.insert(std::make_pair(rtspsession, item)).first; + } + + assert(NULL == transport); + for(size_t i = 0; i < num && !transport; i++) + { + if(RTSP_TRANSPORT_RTP_UDP == transports[i].transport) + { + // RTP/AVP/UDP + transport = &transports[i]; + } + else if(RTSP_TRANSPORT_RTP_TCP == transports[i].transport) + { + // RTP/AVP/TCP + // 10.12 Embedded (Interleaved) Binary Data (p40) + transport = &transports[i]; + } + } + if(!transport) + { + // 461 Unsupported Transport + return rtsp_server_reply_setup(rtsp, 461, NULL, NULL); + } + + rtsp_media_t &item = it->second; + if (RTSP_TRANSPORT_RTP_TCP == transport->transport) + { + // 10.12 Embedded (Interleaved) Binary Data (p40) + int interleaved[2]; + if (transport->interleaved1 == transport->interleaved2) + { + interleaved[0] = item.channel++; + interleaved[1] = item.channel++; + } + else + { + interleaved[0] = transport->interleaved1; + interleaved[1] = transport->interleaved2; + } + + item.transport = std::make_shared(rtsp, interleaved[0], interleaved[1]); + item.media->SetTransport(path_basename(uri), item.transport); + + // RTP/AVP/TCP;interleaved=0-1 + snprintf(rtsp_transport, sizeof(rtsp_transport), "RTP/AVP/TCP;interleaved=%d-%d", interleaved[0], interleaved[1]); + } + else if(transport->multicast) + { + unsigned short port[2] = { transport->rtp.u.client_port1, transport->rtp.u.client_port2 }; + char multicast[65]; + // RFC 2326 1.6 Overall Operation p12 + + if(transport->destination[0]) + { + // Multicast, client chooses address + snprintf(multicast, sizeof(multicast), "%s", transport->destination); + port[0] = transport->rtp.m.port1; + port[1] = transport->rtp.m.port2; + } + else + { + // Multicast, server chooses address + snprintf(multicast, sizeof(multicast), "%s", UDP_MULTICAST_ADDR); + port[0] = UDP_MULTICAST_PORT; + port[1] = UDP_MULTICAST_PORT + 1; + } + + item.transport = std::make_shared(); + if(0 != ((RTPUdpTransport*)item.transport.get())->Init(multicast, port)) + { + // log + + // 500 Internal Server Error + return rtsp_server_reply_setup(rtsp, 500, NULL, NULL); + } + item.media->SetTransport(path_basename(uri), item.transport); + + // Transport: RTP/AVP;multicast;destination=224.2.0.1;port=3456-3457;ttl=16 + snprintf(rtsp_transport, sizeof(rtsp_transport), + "RTP/AVP;multicast;destination=%s;port=%hu-%hu;ttl=%d", + multicast, port[0], port[1], 16); + + // 461 Unsupported Transport + //return rtsp_server_reply_setup(rtsp, 461, NULL, NULL); + } + else + { + // unicast + item.transport = std::make_shared(); + + assert(transport->rtp.u.client_port1 && transport->rtp.u.client_port2); + unsigned short port[2] = { transport->rtp.u.client_port1, transport->rtp.u.client_port2 }; + const char *ip = transport->destination[0] ? transport->destination : rtsp_server_get_client(rtsp, NULL); + if(0 != ((RTPUdpTransport*)item.transport.get())->Init(ip, port)) + { + // log + + // 500 Internal Server Error + return rtsp_server_reply_setup(rtsp, 500, NULL, NULL); + } + item.media->SetTransport(path_basename(uri), item.transport); + + // RTP/AVP;unicast;client_port=4588-4589;server_port=6256-6257;destination=xxxx + snprintf(rtsp_transport, sizeof(rtsp_transport), + "RTP/AVP;unicast;client_port=%hu-%hu;server_port=%hu-%hu%s%s", + transport->rtp.u.client_port1, transport->rtp.u.client_port2, + port[0], port[1], + transport->destination[0] ? ";destination=" : "", + transport->destination[0] ? transport->destination : ""); + } + + return rtsp_server_reply_setup(rtsp, 200, it->first.c_str(), rtsp_transport); +} + +static int rtsp_onplay(void* /*ptr*/, rtsp_server_t* rtsp, const char* uri, const char* session, const int64_t *npt, const double *scale) +{ + std::shared_ptr source; + TSessions::iterator it; + { + AutoThreadLocker locker(s_locker); + it = s_sessions.find(session ? session : ""); + if(it == s_sessions.end()) + { + // 454 Session Not Found + return rtsp_server_reply_play(rtsp, 454, NULL, NULL, NULL); + } + else + { + // uri with track + if (0) + { + // 460 Only aggregate operation allowed + return rtsp_server_reply_play(rtsp, 460, NULL, NULL, NULL); + } + } + + source = it->second.media; + } + if(npt && 0 != source->Seek(*npt)) + { + // 457 Invalid Range + return rtsp_server_reply_play(rtsp, 457, NULL, NULL, NULL); + } + + if(scale && 0 != source->SetSpeed(*scale)) + { + // set speed + assert(*scale > 0); + + // 406 Not Acceptable + return rtsp_server_reply_play(rtsp, 406, NULL, NULL, NULL); + } + + // RFC 2326 12.33 RTP-Info (p55) + // 1. Indicates the RTP timestamp corresponding to the time value in the Range response header. + // 2. A mapping from RTP timestamps to NTP timestamps (wall clock) is available via RTCP. + char rtpinfo[512] = { 0 }; + source->GetRTPInfo(uri, rtpinfo, sizeof(rtpinfo)); + + // for vlc 2.2.2 + // MP4FileSource* mp4 = dynamic_cast(source.get()); + // if(mp4) + // mp4->SendRTCP(system_clock()); + + it->second.status = 1; + return rtsp_server_reply_play(rtsp, 200, npt, NULL, rtpinfo); +} + +static int rtsp_onpause(void* /*ptr*/, rtsp_server_t* rtsp, const char* /*uri*/, const char* session, const int64_t* /*npt*/) +{ + std::shared_ptr source; + TSessions::iterator it; + { + AutoThreadLocker locker(s_locker); + it = s_sessions.find(session ? session : ""); + if(it == s_sessions.end()) + { + // 454 Session Not Found + return rtsp_server_reply_pause(rtsp, 454); + } + else + { + // uri with track + if (0) + { + // 460 Only aggregate operation allowed + return rtsp_server_reply_pause(rtsp, 460); + } + } + + source = it->second.media; + it->second.status = 2; + } + + source->Pause(); + + // 457 Invalid Range + + return rtsp_server_reply_pause(rtsp, 200); +} + +static int rtsp_onteardown(void* /*ptr*/, rtsp_server_t* rtsp, const char* /*uri*/, const char* session) +{ + std::shared_ptr source; + TSessions::iterator it; + { + AutoThreadLocker locker(s_locker); + it = s_sessions.find(session ? session : ""); + if(it == s_sessions.end()) + { + // 454 Session Not Found + return rtsp_server_reply_teardown(rtsp, 454); + } + + source = it->second.media; + s_sessions.erase(it); + } + + return rtsp_server_reply_teardown(rtsp, 200); +} + +static int rtsp_onannounce(void* /*ptr*/, rtsp_server_t* rtsp, const char* uri, const char* sdp, int len) +{ + return rtsp_server_reply_announce(rtsp, 200); +} + +static int rtsp_onrecord(void* /*ptr*/, rtsp_server_t* rtsp, const char* uri, const char* session, const int64_t *npt, const double *scale) +{ + return rtsp_server_reply_record(rtsp, 200, NULL, NULL); +} + +static int rtsp_onoptions(void* ptr, rtsp_server_t* rtsp, const char* uri) +{ + const char* require = rtsp_server_get_header(rtsp, "Require"); + (void)require; + return rtsp_server_reply_options(rtsp, 200); +} + +static int rtsp_ongetparameter(void* ptr, rtsp_server_t* rtsp, const char* uri, const char* session, const void* content, int bytes) +{ + const char* ctype = rtsp_server_get_header(rtsp, "Content-Type"); + const char* encoding = rtsp_server_get_header(rtsp, "Content-Encoding"); + const char* language = rtsp_server_get_header(rtsp, "Content-Language"); + (void)ctype; + (void)encoding; + (void)language; + return rtsp_server_reply_get_parameter(rtsp, 200, NULL, 0); +} + +static int rtsp_onsetparameter(void* ptr, rtsp_server_t* rtsp, const char* uri, const char* session, const void* content, int bytes) +{ + const char* ctype = rtsp_server_get_header(rtsp, "Content-Type"); + const char* encoding = rtsp_server_get_header(rtsp, "Content-Encoding"); + const char* language = rtsp_server_get_header(rtsp, "Content-Language"); + (void)ctype; + (void)encoding; + (void)language; + return rtsp_server_reply_set_parameter(rtsp, 200); +} + +static int rtsp_onclose(void* /*ptr2*/) +{ + // TODO: notify rtsp connection lost + // start a timer to check rtp/rtcp activity + // close rtsp media session on expired + printf("rtsp close\n"); + return 0; +} + +// static void rtsp_onerror(void* /*param*/, rtsp_server_t* rtsp, int code) +// { +// printf("rtsp_onerror code=%d, rtsp=%p\n", code, rtsp); + +// TSessions::iterator it; +// AutoThreadLocker locker(s_locker); +// for (it = s_sessions.begin(); it != s_sessions.end(); ++it) +// { +// if (rtsp == it->second.rtsp) +// { +// it->second.media->Pause(); +// s_sessions.erase(it); +// break; +// } +// } + +// //return 0; +// } + +static int rtsp_send(void* ptr, const void* data, size_t bytes) +{ + socket_t socket = (socket_t)(intptr_t)ptr; + + // TODO: send multiple rtp packet once time + return (int)bytes == socket_send(socket, data, bytes, 0) ? 0 : -1; +} + +#include +#include +#include +#include +#include +static int get_ip(char *hw, char ip[16]) +{ + struct ifaddrs *ifaddr, *ifa; + int family, s; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) == -1) { + perror("getifaddrs"); + return -1; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) { + continue; + } + + family = ifa->ifa_addr->sa_family; + + if (family == AF_INET) { + s = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (s != 0) { + printf("getnameinfo() failed: %s\n", gai_strerror(s)); + return -1; + } + + if (!strcmp(ifa->ifa_name, hw)) { + strncpy(ip, host, 16); + freeifaddrs(ifaddr); + return 0; + } + } + } + + freeifaddrs(ifaddr); + return -1; +} + +#ifdef __cplusplus +extern "C" { +#endif +typedef struct { + bool rtsp_is_init; + bool rtsp_is_start; + socket_t rtsp_socket; + pthread_t rtsp_id; + char ip[16]; + int port; +} priv_t; + +static priv_t priv; + +int rtsp_server_init(char *ip, int port) +{ + socket_t socket; + if (priv.rtsp_is_init) { + return 0; + } + + char new_ip[16] = {0}; + if (ip == NULL) { + if (get_ip((char *)"eth0", new_ip) && get_ip((char *)"usb0", new_ip) && get_ip((char *)"wlan0", new_ip)) { + strcpy(new_ip, "0.0.0.0"); + } + } else { + strcpy(new_ip, ip); + } + + // create server socket + socket = socket_tcp_listen(0 /*AF_UNSPEC*/, new_ip, port, SOMAXCONN, 0, 0); + if (socket_invalid == socket) + return -1; + + strcpy(priv.ip, new_ip); + priv.port = port; + + priv.rtsp_socket = socket; + priv.rtsp_is_init = true; + return 0; +} + +int rtsp_server_deinit(void) +{ + if (!priv.rtsp_is_init) { + return 0; + } + + socket_close(priv.rtsp_socket); + + priv.rtsp_is_init = true; + return 0; +} + +char *rtsp_get_server_ip(void) +{ + return priv.ip; +} + +int rtsp_get_server_port(void) +{ + return priv.port; +} + +void rtsp_send_h265_data(uint8_t *data, size_t data_len) +{ + TSessions::iterator it; + for (it = s_sessions.begin(); it != s_sessions.end(); ++it) + { + rtsp_media_t& session = it->second; + H265CameraSource *media = camera_source; + if(session.status == 1) { + media->SetNextFrame((uint8_t *)data, data_len); + } + } +} + +static void* _rtsp_server_thread(void *args) +{ + (void)args; + socket_t socket; + char buffer[512]; + socket = priv.rtsp_socket; + while(1) + { + sockaddr_storage addr; + socklen_t len = sizeof(addr); + socket_t tcp = socket_accept(socket, &addr, &len); + if (socket_invalid == tcp) + continue; + + struct rtsp_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.ondescribe = rtsp_ondescribe; + handler.onsetup = rtsp_onsetup; + handler.onplay = rtsp_onplay; + handler.onpause = rtsp_onpause; + handler.onteardown = rtsp_onteardown; + handler.onannounce = rtsp_onannounce; + handler.onrecord = rtsp_onrecord; + handler.onoptions = rtsp_onoptions; + handler.ongetparameter = rtsp_ongetparameter; + handler.onsetparameter = rtsp_onsetparameter; + handler.close = rtsp_onclose; + handler.send = rtsp_send; + + u_short port = 0; + socket_setnonblock(tcp, 0); // block io + socket_addr_to((const sockaddr*)&addr, len, buffer, &port); + struct rtsp_server_t* rtsp = rtsp_server_create(buffer, port, &handler, NULL, (void*)(intptr_t)tcp); // reuse-able, don't need create in every link + + while (1) + { + int r = socket_recv_by_time(tcp, buffer, sizeof(buffer), 0, 5); + if (r > 0) + { + size_t n = r; + r = rtsp_server_input(rtsp, buffer, &n); + continue; + } + else if (r <= 0 && r != SOCKET_TIMEDOUT) + { + break; + } + + TSessions::iterator it; + AutoThreadLocker locker(s_locker); + for (it = s_sessions.begin(); it != s_sessions.end(); ++it) + { + rtsp_media_t& session = it->second; + if (1 == session.status) { + session.media->Play(); + } + } + } + + rtsp_server_destroy(rtsp); + socket_close(tcp); + } + + return NULL; +} + +int rtsp_server_start(void) +{ + if (!priv.rtsp_is_init) { + return -1; + } + + if (priv.rtsp_is_start) { + return 0; + } + + if (0 != pthread_create(&priv.rtsp_id, NULL, _rtsp_server_thread, NULL)) { + return -1; + } + + priv.rtsp_is_start = true; + return 0; +} + +int rtsp_server_stop(void) +{ + if (!priv.rtsp_is_init) { + return -1; + } + + if (!priv.rtsp_is_start) { + return 0; + } + + if (0 != pthread_cancel(priv.rtsp_id)) { + return -1; + } + + priv.rtsp_is_start = false; + + return 0; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/vision/include/maix_rtmp.hpp b/components/vision/include/maix_rtmp.hpp new file mode 100644 index 00000000..cbaed950 --- /dev/null +++ b/components/vision/include/maix_rtmp.hpp @@ -0,0 +1,137 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2024.5.15: Add framework, create this file. + */ + +#ifndef __MAIX_RTMP_HPP +#define __MAIX_RTMP_HPP + +#include "maix_basic.hpp" +#include "maix_camera.hpp" +#include +#include +#include + +namespace maix { + /** + * Rtmp class + * @maixpy maix.rtmp.Rtmp + */ + class Rtmp { + std::string _host; + std::string _app; + std::string _stream; + int _port; + + int _socket; + void *_handler; + + camera::Camera *_cam; + thread::Thread *_thread; + pthread_mutex_t _lock; + bool _start; + std::string _path; + public: + /** + * @brief Construct a new Video object + * @note Rtmp url : rtmp://host:prot/app/stream + * example: + * r = Rtmp("localhost", 1935, "live", "stream") + * means rtmp url is rtmp://localhost:1935/live/stream + * @param host rtmp ip + * @param port rtmp port, default is 1935. + * @param app rtmp app name + * @param stream rtmp stream name + * @maixpy maix.rtmp.Rtmp.__init__ + * @maixcdk maix.rtmp.Rtmp.Rtmp + */ + Rtmp(std::string host, int port = 1935, std::string app = std::string(), std::string stream = std::string()); + ~Rtmp(); + + /** + * @brief Push rtmp video data + * @return error code, err::ERR_NONE means success, others means failed + * @maixcdk maix.rtmp.Rtmp.push_video + */ + int push_video(void *data, size_t data_size, uint32_t timestamp); + + /** + * @brief Push rtmp audio data + * @return error code, err::ERR_NONE means success, others means failed + * @maixcdk maix.rtmp.Rtmp.push_audio + */ + int push_audio(void *data, size_t data_size, uint32_t timestamp); + + /** + * @brief Push rtmp script data + * @return error code, err::ERR_NONE means success, others means failed + * @maixcdk maix.rtmp.Rtmp.push_script + */ + int push_script(void *data, size_t data_size, uint32_t timestamp); + + /** + * @brief Bind camera + * @note If the cam object is bound, the cam object cannot be used elsewhere. + * @param cam camera object + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.rtmp.Rtmp.bind_camera + */ + err::Err bind_camera(camera::Camera *cam) { + _cam = cam; + return err::ERR_NONE; + } + + /** + * @brief Start push stream + * @note only support flv file now + * @param path File path, if you passed file path, cyclic push the file, else if you bound camera, push the camera image. + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.rtmp.Rtmp.start + */ + err::Err start(std::string path = std::string()); + + /** + * @brief Stop push + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.rtmp.Rtmp.stop + */ + err::Err stop(); + + /** + * @brief Lock + * @param time lock time, unit:ms + * @return error code, err::ERR_NONE means success, others means failed + * @maixcdk maix.rtmp.Rtmp.lock + */ + err::Err lock(uint32_t time); + + /** + * @brief Unlock + * @return error code, err::ERR_NONE means success, others means failed + * @maixcdk maix.rtmp.Rtmp.unlock + */ + err::Err unlock(); + + /** + * @brief Get the file path of the push stream + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.rtmp.Rtmp.get_path + */ + std::string get_path() { + return _path; + } + + /** + * @brief Check whether push streaming has started + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.rtmp.Rtmp.get_path + */ + bool is_started() { + return _start ? true : false; + } + }; +} + +#endif // __MAIX_RTMP_HPP diff --git a/components/vision/port/linux/maix_rtmp_linux.cpp b/components/vision/port/linux/maix_rtmp_linux.cpp new file mode 100644 index 00000000..503ab6ac --- /dev/null +++ b/components/vision/port/linux/maix_rtmp_linux.cpp @@ -0,0 +1,55 @@ +#include "maix_rtmp.hpp" + +namespace maix { + Rtmp::Rtmp(std::string host, int port, std::string app, std::string stream) { + _host = host; + _port = port; + _app = app; + _stream = stream; + } + + Rtmp::~Rtmp() { + } + + // return 0 ok, other error + int Rtmp::push_video(void *data, size_t data_size, uint32_t timestamp) { + (void)data; + (void)data_size; + (void)timestamp; + return 0; + } + + // return 0 ok, other error + int Rtmp::push_audio(void *data, size_t data_size, uint32_t timestamp) { + (void)data; + (void)data_size; + (void)timestamp; + return 0; + } + + // return 0 ok, other error + int Rtmp::push_script(void *data, size_t data_size, uint32_t timestamp) { + (void)data; + (void)data_size; + (void)timestamp; + return 0; + } + + err::Err Rtmp::lock(uint32_t time) { + (void)time; + return err::ERR_NOT_IMPL; + } + + err::Err Rtmp::unlock() { + return err::ERR_NOT_IMPL; + } + + err::Err Rtmp::start(std::string path) { + (void)path; + return err::ERR_NOT_IMPL; + } + + err::Err Rtmp::stop() { + return err::ERR_NOT_IMPL; + } +} \ No newline at end of file diff --git a/components/vision/port/maixcam/maix_rtmp_maixcam.cpp b/components/vision/port/maixcam/maix_rtmp_maixcam.cpp new file mode 100644 index 00000000..711942fe --- /dev/null +++ b/components/vision/port/maixcam/maix_rtmp_maixcam.cpp @@ -0,0 +1,261 @@ +#include "maix_rtmp.hpp" + +#include +#include "sockutil.h" +#include "sys/system.h" +#include "rtmp-client.h" +#include "flv-reader.h" +#include "flv-proto.h" +#include +#include +#include +#include +#include + +static int rtmp_client_send(void* param, const void* header, size_t len, const void* data, size_t bytes) +{ + socket_t* socket = (socket_t*)param; + socket_bufvec_t vec[2]; + socket_setbufvec(vec, 0, (void*)header, len); + socket_setbufvec(vec, 1, (void*)data, bytes); + + return socket_send_v_all_by_time(*socket, vec, bytes > 0 ? 2 : 1, 0, 5000); +} + +namespace maix { + Rtmp::Rtmp(std::string host, int port, std::string app, std::string stream) { + _host = host; + _port = port; + _app = app; + _stream = stream; + + char packet[20 * 1024]; + snprintf(packet, sizeof(packet), "rtmp://%s/%s", host.c_str(), app.c_str()); // tcurl + struct rtmp_client_handler_t handler; + memset(&handler, 0, sizeof(handler)); + handler.send = rtmp_client_send; + + socket_init(); + socket_t socket = socket_connect_host(_host.c_str(), _port, 2000); + if (socket <= 0) { + throw std::runtime_error("socket connect failed!"); + } + socket_setnonblock(socket, 0); + _socket = socket; + + rtmp_client_t* rtmp = rtmp_client_create(_app.c_str(), _stream.c_str(), packet/*tcurl*/, &_socket, &handler); + int r = rtmp_client_start(rtmp, 0); + while (4 != rtmp_client_getstate(rtmp) && (r = socket_recv(_socket, packet, sizeof(packet), 0)) > 0) + { + int res = rtmp_client_input(rtmp, packet, r); + if (res != 0) { + throw std::runtime_error("rtmp_client_input failed!"); + } + } + + if (0 != pthread_mutex_init(&_lock, NULL)) { + throw std::runtime_error("create lock failed!"); + } + + _handler = rtmp; + _thread = nullptr; + _cam = nullptr; + _path = std::string(); + } + + Rtmp::~Rtmp() { + rtmp_client_t* rtmp = (rtmp_client_t *)_handler; + rtmp_client_destroy(rtmp); + socket_close(_socket); + socket_cleanup(); + + pthread_mutex_destroy(&_lock); + } + + // return 0 ok, other error + int Rtmp::push_video(void *data, size_t data_size, uint32_t timestamp) { + rtmp_client_t* rtmp = (rtmp_client_t *)_handler; + if (rtmp == nullptr) { + throw std::runtime_error("rtmp hander is null!"); + } + return rtmp_client_push_video(rtmp, data, data_size, timestamp); + } + + // return 0 ok, other error + int Rtmp::push_audio(void *data, size_t data_size, uint32_t timestamp) { + rtmp_client_t* rtmp = (rtmp_client_t *)_handler; + if (rtmp == nullptr) { + throw std::runtime_error("rtmp hander is null!"); + } + return rtmp_client_push_audio(rtmp, data, data_size, timestamp); + } + + // return 0 ok, other error + int Rtmp::push_script(void *data, size_t data_size, uint32_t timestamp) { + rtmp_client_t* rtmp = (rtmp_client_t *)_handler; + if (rtmp == nullptr) { + throw std::runtime_error("rtmp hander is null!"); + } + return rtmp_client_push_script(rtmp, data, data_size, timestamp); + } + + static void _push_file_thread(void *args) { + Rtmp *rtmp = (Rtmp *)args; + rtmp->lock(-1); + std::string path = rtmp->get_path(); + rtmp->unlock(); + + int r, type; + int aacconfig = 0; + size_t taglen; + uint32_t timestamp; + uint32_t s_timestamp = 0; + uint32_t diff = 0; + uint64_t clock = 0; + size_t packet_size = 1024 * 1024; + char *packet = (char *)malloc(packet_size); + if (packet == nullptr) { + log::error("rtmp thread malloc failed!\r\n"); + return; + } + + while (1) { + rtmp->lock(-1); + if (!rtmp->is_started()) { + rtmp->unlock(); + break; + } + void *f = ::flv_reader_create(&path[0]); + rtmp->unlock(); + if (f == nullptr) { + log::error("Find not path %s!\r\n", &path[0]); + break; + } + + clock = time::time_ms(); + while (1 == flv_reader_read(f, &type, ×tamp, &taglen, packet, packet_size)) + { + // log::debug("[%ld]type:%d timestemp:%d taglen:%ld\r\n", time::time_ms(), type, timestamp, taglen); + + rtmp->lock(-1); + if (!rtmp->is_started()) { + rtmp->unlock(); + break; + } + + uint64_t t = time::time_ms(); + if (clock + timestamp > t && clock + timestamp < t + 3 * 1000) // dts skip + { + // log::info("skip dts, sleep %d ms\r\n", clock + timestamp - t); + time::sleep_ms(clock + timestamp - t); + } + else if (clock + timestamp > t + 3 * 1000) { + clock = t - timestamp; + } + + timestamp += diff; + s_timestamp = timestamp > s_timestamp ? timestamp : s_timestamp; + if (8 == type) + { + if (0 == packet[1]) + { + if(0 != aacconfig) + continue; + aacconfig = 1; + } + r = rtmp->push_audio(packet, taglen, timestamp); + } + else if (9 == type) + { + r = rtmp->push_video(packet, taglen, timestamp); + } + else if (18 == type) + { + r = rtmp->push_script(packet, taglen, timestamp); + } + else + { + r = 0; // ignore + } + + if (0 != r) + { + log::error("send failed! r = %d\r\n", r); + rtmp->unlock(); + break; + } + rtmp->unlock(); + } + flv_reader_destroy(f); + diff = s_timestamp + 30; + } + + if (packet) { + free(packet); + packet = nullptr; + } + } + + err::Err Rtmp::lock(uint32_t time) { + uint32_t count = 0; + while (0 != pthread_mutex_trylock(&_lock)) { + count ++; + if (count >= time) { + break; + } + time::sleep_ms(1); + } + + if (count >= time) + return err::ERR_BUSY; + else + return err::ERR_NONE; + } + + err::Err Rtmp::unlock() { + if (0 == pthread_mutex_unlock(&_lock)) { + return err::ERR_NONE; + } + return err::ERR_RUNTIME; + } + + err::Err Rtmp::start(std::string path) { + lock(-1); + if (_start == true) { + return err::ERR_BUSY; + } + + if (path.size() > 0) { + if (fs::splitext(path) != ".flv") { + log::error("check file path failed!\r\n"); + return err::ERR_RUNTIME; + } + + _path = path; + _start = true; + _thread = new thread::Thread(_push_file_thread, this); + if (this->_thread == NULL) { + log::error("create camera thread failed!\r\n"); + return err::ERR_RUNTIME; + } + } else { + if (_cam == nullptr) { + log::error("you need bind camera first!\r\n"); + return err::ERR_RUNTIME; + } + } + + unlock(); + return err::ERR_NONE; + } + + err::Err Rtmp::stop() { + lock(-1); + _start = false; + unlock(); + + _thread->join(); + _thread = nullptr; + return err::ERR_NONE; + } +} \ No newline at end of file diff --git a/components/vision/port/maixcam/maix_rtsp_maixcam.cpp b/components/vision/port/maixcam/maix_rtsp_maixcam.cpp index b4627ff4..033c961b 100644 --- a/components/vision/port/maixcam/maix_rtsp_maixcam.cpp +++ b/components/vision/port/maixcam/maix_rtsp_maixcam.cpp @@ -7,7 +7,7 @@ #include "maix_rtsp.hpp" #include "maix_err.hpp" -#include "rtsp-server.hpp" +#include "rtsp_server.h" #include #include "sophgo_middleware.hpp" diff --git a/examples/rtmp_demo/.gitignore b/examples/rtmp_demo/.gitignore new file mode 100644 index 00000000..7171eaac --- /dev/null +++ b/examples/rtmp_demo/.gitignore @@ -0,0 +1,9 @@ +build +dist +.config.mk +.flash.conf.json +data + +/CMakeLists.txt + +__pycache__ diff --git a/examples/rtmp_demo/README.md b/examples/rtmp_demo/README.md new file mode 100644 index 00000000..2ce5c828 --- /dev/null +++ b/examples/rtmp_demo/README.md @@ -0,0 +1,5 @@ +Hello World Project based on MaixCDK +==== + +Hello world example code for MaixCDK of Sipeed, build method please visit [MaixCDK](https://github.com/sipeed/MaixCDK). + diff --git a/examples/rtmp_demo/app.yaml b/examples/rtmp_demo/app.yaml new file mode 100644 index 00000000..a4b4bf3e --- /dev/null +++ b/examples/rtmp_demo/app.yaml @@ -0,0 +1,11 @@ +id: rtmp_demo +name: RTMP demo +name[zh]: rtmp示例 +version: 1.0.0 +#icon: assets/hello.png +author: Sipeed Ltd +desc: RTMP demo +desc[zh]: rtmp示例 +files: + # assets: assets + diff --git a/examples/rtmp_demo/main/CMakeLists.txt b/examples/rtmp_demo/main/CMakeLists.txt new file mode 100644 index 00000000..eaf23d82 --- /dev/null +++ b/examples/rtmp_demo/main/CMakeLists.txt @@ -0,0 +1,74 @@ +############### Add include ################### +list(APPEND ADD_INCLUDE "include" + ) +list(APPEND ADD_PRIVATE_INCLUDE "") +############################################### + +############ Add source files ################# +# list(APPEND ADD_SRCS "src/main.c" +# "src/test.c" +# ) +append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +# list(REMOVE_ITEM COMPONENT_SRCS "src/test2.c") +# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c") +# FILE(GLOB EXTRA_SRC "src/*.c") +# list(APPEND ADD_SRCS ${EXTRA_SRC}) +# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS +# append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") +# set(ADD_ASM_SRCS "src/asm.S") +# list(APPEND ADD_SRCS ${ADD_ASM_SRCS}) +# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language +# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB") +############################################### + +###### Add required/dependent components ###### +list(APPEND ADD_REQUIREMENTS basic media_server vision) +############################################### + +###### Add link search path for requirements/libs ###### +# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib") +# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here +# set (OpenCV_DIR opencv/lib/cmake/opencv4) +# find_package(OpenCV REQUIRED) +############################################### + +############ Add static libs ################## +# list(APPEND ADD_STATIC_LIB "lib/libtest.a") +############################################### + +#### Add compile option for this component #### +#### Just for this component, won't affect other +#### modules, including component that depend +#### on this component +# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1) + +#### Add compile option for this component +#### and components denpend on this component +# list(APPEND ADD_DEFINITIONS -DAAAAA222=1 +# -DAAAAA333=1) +############################################### + +############ Add static libs ################## +#### Update parent's variables like CMAKE_C_LINK_FLAGS +# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE) +############################################### + +######### Add files need to download ######### +# list(APPEND ADD_FILE_DOWNLOADS "{ +# 'url': 'https://*****/abcde.tar.xz', +# 'urls': [], # backup urls, if url failed, will try urls +# 'sites': [], # download site, user can manually download file and put it into dl_path +# 'sha256sum': '', +# 'filename': 'abcde.tar.xz', +# 'path': 'toolchains/xxxxx', +# 'check_files': [] +# }" +# ) +# +# then extracted file in ${DL_EXTRACTED_PATH}/toolchains/xxxxx, +# you can directly use then, for example use it in add_custom_command +############################################## + +# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib +register_component() diff --git a/examples/rtmp_demo/main/Kconfig b/examples/rtmp_demo/main/Kconfig new file mode 100644 index 00000000..e69de29b diff --git a/examples/rtmp_demo/main/include/main.h b/examples/rtmp_demo/main/include/main.h new file mode 100644 index 00000000..45dcbb04 --- /dev/null +++ b/examples/rtmp_demo/main/include/main.h @@ -0,0 +1,3 @@ +#pragma once + + diff --git a/examples/rtmp_demo/main/src/main.cpp b/examples/rtmp_demo/main/src/main.cpp new file mode 100644 index 00000000..a6d6b2da --- /dev/null +++ b/examples/rtmp_demo/main/src/main.cpp @@ -0,0 +1,86 @@ + +#include "maix_rtmp.hpp" +#include "maix_basic.hpp" +#include "maix_vision.hpp" +#include "main.h" + +#include +#include +#include +#include +#include + +using namespace maix; + +static int helper(void) +{ + printf( "========================\r\n" + "Intput param:\r\n" + "0 : rtmp client, push file\r\n" + " example: ./test_media_server 0 192.168.0.30 myapp stream ./test.flv" + "1 : rtmp client, push camera image\r\n" + "========================\r\n"); + fflush(stdin); + return 0; +} + +int _main(int argc, char* argv[]) +{ + int cmd = 0; + if (argc > 1) { + cmd = atoi(argv[1]); + } else { + helper(); + return 0; + } + + switch (cmd) { + case 0: + { + image::Image img = image::Image(); + if (argc < 6) { + helper(); + break; + } + std::string host = argv[2]; + int port = 1935; + std::string app = argv[3]; + std::string stream = argv[4]; + std::string file = argv[5]; + printf("push %s to rtmp://%s:%d/%s/%s!\r\n", &file[0], &host[0], port, &app[0], &stream[0]); + + Rtmp rtmp = Rtmp(host, port, app, stream); + + log::info("start\r\n"); + rtmp.start(file); + + while (!app::need_exit()) { + log::info("run..\r\n"); + time::sleep(1); + } + rtmp.stop(); + log::info("stop\r\n"); + + break; + } + default: + { + helper(); + break; + } + } + return 0; +} + +int main(int argc, char* argv[]) +{ + // Catch SIGINT signal(e.g. Ctrl + C), and set exit flag to true. + signal(SIGINT, [](int sig){ app::set_exit_flag(true); }); + + // Use CATCH_EXCEPTION_RUN_RETURN to catch exception, + // if we don't catch exception, when program throw exception, the objects will not be destructed. + // So we catch exception here to let resources be released(call objects' destructor) before exit. + CATCH_EXCEPTION_RUN_RETURN(_main, -1, argc, argv); +} + +