From 6f56bf0fe36f862f58fe5a1c20e653fd52ceda87 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 12 Mar 2025 21:00:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BD=91=E7=BB=9C=E7=9F=AD?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/CMakeLists.txt | 2 +- app/src/main/cpp/PhoneDevice.cpp | 91 +++++++++++++- app/src/main/cpp/PhoneDevice.h | 3 + app/src/main/cpp/media/RTSPRecorder.cpp | 44 ++++++- app/src/main/cpp/media/RTSPRecorder.h | 5 +- app/src/main/cpp/media/Streaming.cpp | 159 ++++++++++++++++++++++++ app/src/main/cpp/media/Streaming.h | 50 ++++++++ 7 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 app/src/main/cpp/media/Streaming.cpp create mode 100644 app/src/main/cpp/media/Streaming.h diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 36093aca..9ecc18f2 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -154,7 +154,7 @@ add_definitions(-DDISABLE_RTTI) # include_directories( ${HDRPLUS_ROOT}/${ANDROID_ABI}/include/ZLToolKit/src/ ) # SET(ZLMEDIAKIT_LIBS ${ZLMEDIAKIT_LIBS} zlmediakit zltoolkit) -SET(STREAMING_SRCS media/RTSPToMP4.cpp media/RTSPRecorder.cpp ) +SET(STREAMING_SRCS media/RTSPToMP4.cpp media/RTSPRecorder.cpp media/Streaming.cpp ) SET(HDRPLUS_LIBS raw exiv2 exiv2-xmp expat lcms2 OpenMP::OpenMP_CXX) diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 8eabf34b..f5de977c 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -9,6 +9,7 @@ #include "CvText.h" #include "PositionHelper.h" #include "DngCreator.h" +#include "media/Streaming.h" #include "netcamera/VendorCtrl.h" #include "netcamera/YuShiCtrl.h" @@ -1775,7 +1776,7 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c std::string tmpFile = m_appPath + (APP_PATH_TMP DIR_SEP_STR) + std::to_string(localPhotoInfo.photoId) + ".mp4"; // RTSPToMP4 dumper(netPhotoInfo.url, tmpFile.c_str(), localPhotoInfo.duration * 1000); // dumper.start(); - dumpRtspToMp4(streamingUrl.c_str(), tmpFile.c_str(), localPhotoInfo.duration * 1000); + dumpRtspToMp4(streamingUrl.c_str(), tmpFile.c_str(), localPhotoInfo.duration * 1000, GetEthnetHandle()); ethernetPowerCtrl.reset(); XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power OFF"); @@ -1803,8 +1804,92 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c return true; } -bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& url, std::vector& osds, std::shared_ptr powerCtrlPtr) +bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std::string& url, std::vector& osds, std::shared_ptr powerCtrlPtr) { +#if 0 + if (photoInfo.mediaType == XY_MEDIA_TYPE_STREAM) + { + std::map >::iterator it = m_streamings.find(photoInfo.channel); + if (it != m_streamings.end()) + { + it->second->stop(); + it->second.reset(); + m_streamings.erase(it); + } + + NET_PHOTO_INFO netPhotoInfo = { 0, 0 }; + if (photoInfo.vendor == 1) + { + // Hai Kang + netPhotoInfo.authType = HTTP_AUTH_TYPE_DIGEST; + snprintf(netPhotoInfo.url, sizeof(netPhotoInfo.url), "/ISAPI/Streaming/channels/1/picture?"); + } + else if (photoInfo.vendor == 2) + { + // Hang Yu + strcpy(netPhotoInfo.url, "/cgi-bin/snapshot.cgi"); + } + else if (photoInfo.vendor == 3) + { + // Yu Shi + netPhotoInfo.authType = HTTP_AUTH_TYPE_DIGEST; + int streamSid = 0; // should put into config + // rtsp://192.168.0.13:554/media/video1 + snprintf(netPhotoInfo.url, sizeof(netPhotoInfo.url), "/media/video%u", (uint32_t)photoInfo.cameraId); + // strcpy(netPhotoInfo.url, "rtsp://192.168.50.224/live/0"); + } + else if (photoInfo.vendor == 5) + { + // Hang Yu - New + netPhotoInfo.authType = HTTP_AUTH_TYPE_BASIC; + // http://192.168.1.46/Snapshot/%u/RemoteImageCapture?ImageFormat=2&HorizontalPixel=1920&VerticalPixel=1080 + // http://192.168.1.101/Snapshot/1/2/RemoteImageCaptureV2?ImageFormat=jpg + // http://192.168.1.101/Snapshot/1/1/RemoteImageCaptureV2?ImageFormat=jpg + snprintf(netPhotoInfo.url, sizeof(netPhotoInfo.url), "/Snapshot/%u/1/RemoteImageCaptureV2?ImageFormat=jpg", (uint32_t)photoInfo.cameraId); + } + else + { + XYLOG(XYLOG_SEVERITY_ERROR, "Vendor(%u) not Supported CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.vendor, (uint32_t)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId); + TakePhotoCb(0, photoInfo, "", 0); + return false; + } + + StreamForwarder* forwarder = new StreamForwarder(); + m_streamings[photoInfo.channel] = std::shared_ptr((Streaming*)forwarder); + // Initialize with RTSP input and RTMP output + if (!forwarder->initialize(std::string(netPhotoInfo.url), url)) { + std::cerr << "Failed to initialize stream forwarder" << std::endl; + return -1; + } + + // Optional: Set callback to process video frames +#if 0 + forwarder->setFrameCallback([](uint8_t* data, int linesize, int width, int height) { + // Process frame data here + // Example: Add OSD overlay + }); +#endif + + // Start forwarding + forwarder->start(); + + // Wait for user input to stop + // std::cout << "Press Enter to stop streaming..." << std::endl; + // std::cin.get(); + + // forwarder.stop(); + } + else if (photoInfo.mediaType == XY_MEDIA_TYPE_STREAM_OFF) + { + auto it = m_streamings.find(photoInfo.channel); + if (it != m_streamings.end()) + { + it->second->stop(); + it->second.reset(); + m_streamings.erase(it); + } + } +#endif return true; } @@ -4949,4 +5034,4 @@ VendorCtrl* CPhoneDevice::MakeVendorCtrl(int vendor, uint8_t channel, const std: vendorCtrl = new HangYuCtrl(ip, userName, password, channel, netHandle); } return vendorCtrl; -} \ No newline at end of file +} diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index 49033047..daced027 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -155,6 +155,7 @@ void MatToBitmap(JNIEnv *env, cv::Mat& mat, jobject& bitmap) { class PowerControl; class VendorCtrl; +class Streaming; class CPhoneDevice : public IDevice { @@ -425,6 +426,8 @@ protected: std::atomic m_collecting; unsigned long long localDelayTime; + std::map > m_streamings; + }; diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index 11edccfa..85cbf668 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include extern "C" { #include #include @@ -21,15 +22,17 @@ extern "C" { #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) -void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration) +void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration, net_handle_t netHandle) { AVFormatContext* inputFormatContext = nullptr; AVFormatContext* outputFormatContext = nullptr; AVPacket packet; + AVDictionary *options = NULL; av_register_all(); avformat_network_init(); + // Open input RTMP stream if (avformat_open_input(&inputFormatContext, rtmpUrl, nullptr, nullptr) != 0) { fprintf(stderr, "Could not open input file '%s'\n", rtmpUrl); @@ -126,28 +129,61 @@ void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duratio } -void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration) +void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration, net_handle_t netHandle) { AVFormatContext* inputFormatContext = nullptr; AVFormatContext* outputFormatContext = nullptr; AVPacket packet; + AVDictionary *options = NULL; + int res = 0; av_register_all(); avformat_network_init(); + // Set RTSP transport protocol option before opening + av_dict_set(&options, "rtsp_transport", "tcp", 0); + + // Set custom socket options via protocol whitelist and options + inputFormatContext->protocol_whitelist = av_strdup("file,udp,rtp,tcp,rtsp"); + // Open input RTSP stream if (avformat_open_input(&inputFormatContext, rtspUrl, nullptr, nullptr) != 0) { - fprintf(stderr, "Could not open input file '%s'\n", rtspUrl); + // fprintf(stderr, "Could not open input file '%s'\n", rtspUrl); return; } // Retrieve input stream information if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) { - fprintf(stderr, "Could not find stream information\n"); + // fprintf(stderr, "Could not find stream information\n"); avformat_close_input(&inputFormatContext); return; } + // Get socket file descriptor + if (NETWORK_UNSPECIFIED != netHandle) + { + int fd = -1; + if (inputFormatContext->pb) { + AVIOContext *io_ctx = inputFormatContext->pb; + // const char *url = io_ctx->filename; + + // You can access socket options using av_opt API + res = av_opt_get_int(io_ctx, "fd", AV_OPT_SEARCH_CHILDREN, (int64_t*)&fd); + if (res >= 0 && fd >= 0) { + // printf("Socket file descriptor: %d\n", fd); + + int res = android_setsocknetwork(netHandle, fd); + if (res == -1) + { + int errcode = errno; + // printf("android_setsocknetwork errno=%d", errcode); + // XYLOG(XYLOG_SEVERITY_ERROR,"setsocknetwork -1, errcode=%d",errcode); + } + } + } + } + + // Open output MP4 file if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mp4", outputPath) < 0) { fprintf(stderr, "Could not create output context\n"); diff --git a/app/src/main/cpp/media/RTSPRecorder.h b/app/src/main/cpp/media/RTSPRecorder.h index 1133c8e0..c406a43e 100644 --- a/app/src/main/cpp/media/RTSPRecorder.h +++ b/app/src/main/cpp/media/RTSPRecorder.h @@ -6,10 +6,11 @@ #define MICROPHOTO_RTSPRECORDER_H #include +#include // void dumpRtspToMp4(const std::string &rtspUrl, const std::string &outputPath, uint32_t durationInMs); -void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration); -void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration); +void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration, net_handle_t netHandle); +void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration, net_handle_t netHandle); class RTSPRecorder { diff --git a/app/src/main/cpp/media/Streaming.cpp b/app/src/main/cpp/media/Streaming.cpp new file mode 100644 index 00000000..a0bd9b3a --- /dev/null +++ b/app/src/main/cpp/media/Streaming.cpp @@ -0,0 +1,159 @@ +// +// Created by Matthew on 2025/3/11. +// + +#include "Streaming.h" + +#include +#include +#include +#include +#include + +#if 0 +StreamForwarder::~StreamForwarder() { + stop(); + if (inputCtx) { + avformat_close_input(&inputCtx); + } + if (outputCtx) { + if (outputCtx->pb) { + avio_closep(&outputCtx->pb); + } + avformat_free_context(outputCtx); + } +} + +bool StreamForwarder::initialize(const std::string& inputUrl, const std::string& outputUrl) { + if (!openInput(inputUrl)) { + return false; + } + + if (!openOutput(outputUrl)) { + return false; + } + + return true; +} + +bool StreamForwarder::openInput(const std::string& inputUrl) { + inputCtx = avformat_alloc_context(); + if (!inputCtx) { + return false; + } + + if (avformat_open_input(&inputCtx, inputUrl.c_str(), nullptr, nullptr) < 0) { + return false; + } + + if (avformat_find_stream_info(inputCtx, nullptr) < 0) { + return false; + } + + return true; +} + +bool StreamForwarder::openOutput(const std::string& outputUrl) { + int ret = avformat_alloc_output_context2(&outputCtx, nullptr, "flv", outputUrl.c_str()); + if (ret < 0) { + return false; + } + + // Copy streams from input to output + for (unsigned int i = 0; i < inputCtx->nb_streams; i++) { + AVStream* inStream = inputCtx->streams[i]; + AVStream* outStream = avformat_new_stream(outputCtx, inStream->codec->codec); + if (!outStream) { + return false; + } + + ret = avcodec_copy_context(outStream->codec, inStream->codec); + if (ret < 0) { + return false; + } + } + + // Open output file + if (!(outputCtx->oformat->flags & AVFMT_NOFILE)) { + ret = avio_open(&outputCtx->pb, outputUrl.c_str(), AVIO_FLAG_WRITE); + if (ret < 0) { + return false; + } + } + + // Write header + ret = avformat_write_header(outputCtx, nullptr); + if (ret < 0) { + return false; + } + + return true; +} + +void StreamForwarder::setFrameCallback(std::function callback) { + frameCallback = callback; +} + +void StreamForwarder::start() { + isRunning = true; + forwardPackets(); +} + +void StreamForwarder::stop() { + isRunning = false; +} + +void StreamForwarder::forwardPackets() { + AVPacket packet; + AVFrame* frame = av_frame_alloc(); + + while (isRunning) { + if (av_read_frame(inputCtx, &packet) < 0) { + break; + } + + // Process video frames if callback is set + if (frameCallback && packet.stream_index == 0) { // Assuming video is stream 0 + AVCodecContext* codecCtx = inputCtx->streams[packet.stream_index]->codec; + int ret = avcodec_send_packet(codecCtx, &packet); + if (ret < 0) { + continue; + } + + while (ret >= 0) { + ret = avcodec_receive_frame(codecCtx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + goto end; + } + + processFrame(frame); + } + } + + // Forward packet + av_packet_rescale_ts(&packet, + inputCtx->streams[packet.stream_index]->time_base, + outputCtx->streams[packet.stream_index]->time_base); + + int ret = av_interleaved_write_frame(outputCtx, &packet); + if (ret < 0) { + break; + } + + av_packet_unref(&packet); + } + + end: + av_frame_free(&frame); + av_write_trailer(outputCtx); +} + +void StreamForwarder::processFrame(AVFrame* frame) { + if (frameCallback) { + frameCallback(frame->data[0], frame->linesize[0], + frame->width, frame->height); + } +} +#endif \ No newline at end of file diff --git a/app/src/main/cpp/media/Streaming.h b/app/src/main/cpp/media/Streaming.h new file mode 100644 index 00000000..9819f40f --- /dev/null +++ b/app/src/main/cpp/media/Streaming.h @@ -0,0 +1,50 @@ +// +// Created by Matthew on 2025/3/11. +// + +#ifndef MICROPHOTO_STREAMING_H +#define MICROPHOTO_STREAMING_H + +#include +#include + +#include + +extern "C" { +#include +#include +#include +#include +} + +class Streaming +{ +public: + virtual ~Streaming() {} + virtual void start() {} + virtual void stop() {} +}; +#if 0 +class StreamForwarder : public Streaming +{ +private: + AVFormatContext* inputCtx = nullptr; + AVFormatContext* outputCtx = nullptr; + bool isRunning = false; + +public: + StreamForwarder() = default; + virtual ~StreamForwarder(); + + bool initialize(const std::string& inputUrl, const std::string& outputUrl); + virtual void start(); + virtual void stop(); + +private: + bool openInput(const std::string& inputUrl); + bool openOutput(const std::string& outputUrl); + void forwardPackets(); +}; +#endif + +#endif //MICROPHOTO_STREAMING_H