From 64316cedf3bb230720e502ec8b527a06a0c17191 Mon Sep 17 00:00:00 2001 From: liuguijing <1440265357@qq.com> Date: Thu, 13 Mar 2025 16:00:32 +0800 Subject: [PATCH 01/20] Update MpMaster version to 1.1.12 --- mpmaster/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpmaster/build.gradle b/mpmaster/build.gradle index 484f90f3..d43bbd68 100644 --- a/mpmaster/build.gradle +++ b/mpmaster/build.gradle @@ -4,7 +4,7 @@ plugins { def AppMajorVersion = 1 def AppMinorVersion = 1 -def AppBuildNumber = 11 +def AppBuildNumber = 12 def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber From b4e7cf8fab59760998c139714431ddf9c2297918 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 18 Mar 2025 16:23:45 +0800 Subject: [PATCH 02/20] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E6=91=84=E5=83=8F=E6=9C=BA=E6=8B=8D=E6=91=84=E7=9F=AD=E8=A7=86?= =?UTF-8?q?=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/GPIOControl.h | 2 +- app/src/main/cpp/PhoneDevice.cpp | 8 +- app/src/main/cpp/media/RTSPRecorder.cpp | 236 ++++++++++++++---- app/src/main/cpp/media/RTSPRecorder.h | 2 +- app/src/main/cpp/netcamera/HangYuCtrl.cpp | 2 +- app/src/main/cpp/netcamera/VendorCtrl.cpp | 2 +- app/src/main/cpp/netcamera/VendorCtrl.h | 2 +- app/src/main/cpp/netcamera/YuShiCtrl.cpp | 50 +++- app/src/main/cpp/netcamera/httpclient.cpp | 1 + .../com/xypower/common/FileDownloader.java | 12 +- 10 files changed, 255 insertions(+), 62 deletions(-) diff --git a/app/src/main/cpp/GPIOControl.h b/app/src/main/cpp/GPIOControl.h index c7ca9015..b5d37b2d 100644 --- a/app/src/main/cpp/GPIOControl.h +++ b/app/src/main/cpp/GPIOControl.h @@ -583,7 +583,7 @@ public: PowerControl(CMD_SET_12V_EN_STATE, closeDelayTime) #else // USING_PLZ // MicroPhoto - PowerControl(CMD_SET_12V_EN_STATE, CMD_SET_OTG_STATE, closeDelayTime) + PowerControl(CMD_SET_12V_EN_STATE, CMD_SET_OTG_STATE, CMD_SET_485_EN_STATE, closeDelayTime) #endif // USING_PLZ #endif // USING_N938 { diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 48274fd0..132cbe7c 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1822,7 +1822,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, GetEthnetHandle()); + dumpRtspToMp4(streamingUrl.c_str(), tmpFile.c_str(), localPhotoInfo.duration * 1000, localPhotoInfo.userName, localPhotoInfo.password, GetEthnetHandle()); ethernetPowerCtrl.reset(); XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power OFF"); @@ -1832,7 +1832,7 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c if (existsFile(tmpFile)) { std::rename(tmpFile.c_str(), fullPath.c_str()); - TakePhotoCb(3, localPhotoInfo, "", localPhotoInfo.photoTime); + TakePhotoCb(3, localPhotoInfo, fullPath, localPhotoInfo.photoTime); } else { @@ -5079,5 +5079,9 @@ VendorCtrl* CPhoneDevice::MakeVendorCtrl(int vendor, uint8_t channel, const std: // Hang Yu - New vendorCtrl = new HangYuCtrl(ip, userName, password, channel, netHandle); } + if (vendorCtrl != NULL) + { + vendorCtrl->UpdateTime(time(NULL)); + } return vendorCtrl; } diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index 85cbf668..a7324a34 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -12,6 +12,7 @@ extern "C" { #include #include #include +#include } @@ -21,18 +22,100 @@ extern "C" { #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#include +#include + +void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) { + // Map FFmpeg log levels to Android log levels + int android_log_level; + switch (level) { + case AV_LOG_PANIC: + case AV_LOG_FATAL: + android_log_level = ANDROID_LOG_FATAL; + break; + case AV_LOG_ERROR: + android_log_level = ANDROID_LOG_ERROR; + break; + case AV_LOG_WARNING: + android_log_level = ANDROID_LOG_WARN; + break; + case AV_LOG_INFO: + android_log_level = ANDROID_LOG_INFO; + break; + case AV_LOG_VERBOSE: + android_log_level = ANDROID_LOG_VERBOSE; + break; + case AV_LOG_DEBUG: + case AV_LOG_TRACE: + android_log_level = ANDROID_LOG_DEBUG; + break; + default: + android_log_level = ANDROID_LOG_INFO; + break; + } + + // Format the log message + char log_message[1024]; + vsnprintf(log_message, sizeof(log_message), fmt, vl); + + // Send the log message to logcat + __android_log_print(android_log_level, "FFmpeg", "%s", log_message); +} + + +int setup_output_streams(AVFormatContext *input_ctx, AVFormatContext *output_ctx) { + // Copy streams and fix time_base + for (unsigned int i = 0; i < input_ctx->nb_streams; i++) { + AVStream *in_stream = input_ctx->streams[i]; + AVStream *out_stream = avformat_new_stream(output_ctx, NULL); + if (!out_stream) { + return AVERROR_UNKNOWN; + } + + // Copy codec parameters + int ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); + if (ret < 0) { + return ret; + } + + // Fix time base + out_stream->time_base = in_stream->time_base; + + // Clear any existing flags + out_stream->codecpar->codec_tag = 0; + } + return 0; +} + +int write_mp4_header(AVFormatContext *output_ctx) { + AVDictionary *opts = NULL; + + // MP4 specific options + av_dict_set(&opts, "movflags", "faststart+frag_keyframe", 0); + av_dict_set(&opts, "brand", "mp42", 0); + + // Write header + int ret = avformat_write_header(output_ctx, &opts); + if (ret < 0) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + fprintf(stderr, "Header write failed: %s (code: %d)\n", errbuf, ret); + } + + av_dict_free(&opts); + return ret; +} + 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); @@ -129,29 +212,45 @@ void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duratio } -void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration, net_handle_t netHandle) +void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration, const std::string& userName, const std::string& password, 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 +#ifndef NDEBUG + + // Set the custom log callback + av_log_set_callback(ffmpeg_log_callback); + av_log_set_level(AV_LOG_TRACE); + +#endif + + AVDictionary* options = NULL; av_dict_set(&options, "rtsp_transport", "tcp", 0); + av_dict_set(&options, "stimeout", "5000000", 0); + if (!userName.empty()) + { + // av_dict_set(&options, "rtsp_user", userName.c_str(), 0); // Replace with actual username + // av_dict_set(&options, "rtsp_password", password.c_str(), 0); // Replace with actual password + } - // 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) { + int res = avformat_open_input(&inputFormatContext, rtspUrl, nullptr, &options); + av_dict_free(&options); + if (res != 0) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(res, errbuf, AV_ERROR_MAX_STRING_SIZE); + fprintf(stderr, "Could not open input: %s (error code: %d)\n", errbuf, res); // 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"); @@ -159,31 +258,6 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio 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"); @@ -194,21 +268,44 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio // Copy stream information from input to output for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) { AVStream* inStream = inputFormatContext->streams[i]; - AVStream* outStream = avformat_new_stream(outputFormatContext, nullptr); - if (!outStream) { - fprintf(stderr, "Failed to allocate output stream\n"); - avformat_close_input(&inputFormatContext); - avformat_free_context(outputFormatContext); - return; + const AVCodecParameters *in_codecpar = inStream->codecpar; + + if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // Copy video stream as-is + const AVCodec *codec = avcodec_find_decoder(in_codecpar->codec_id); + AVStream *out_stream = avformat_new_stream(outputFormatContext, codec); + if (!out_stream) { + return; + } + avcodec_parameters_copy(out_stream->codecpar, in_codecpar); + out_stream->codecpar->codec_tag = 0; + out_stream->time_base = (AVRational){1, 90000}; + out_stream->avg_frame_rate = inStream->avg_frame_rate; } + else if (in_codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // Setup AAC audio stream + const AVCodec *aac_encoder = avcodec_find_encoder(AV_CODEC_ID_AAC); + if (!aac_encoder) { + fprintf(stderr, "AAC encoder not found\n"); + return; + } - if (avcodec_parameters_copy(outStream->codecpar, inStream->codecpar) < 0) { - fprintf(stderr, "Failed to copy codec parameters\n"); - avformat_close_input(&inputFormatContext); - avformat_free_context(outputFormatContext); - return; + AVStream *out_stream = avformat_new_stream(outputFormatContext, aac_encoder); + if (!out_stream) { + return; + } + + // Set AAC parameters + out_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + out_stream->codecpar->codec_id = AV_CODEC_ID_AAC; + out_stream->codecpar->sample_rate = in_codecpar->sample_rate; + out_stream->codecpar->format = AV_SAMPLE_FMT_FLTP; + out_stream->codecpar->channels = in_codecpar->channels; + out_stream->codecpar->channel_layout = av_get_default_channel_layout(in_codecpar->channels); + out_stream->codecpar->bit_rate = 128000; + out_stream->codecpar->frame_size = 1024; // AAC frame size + out_stream->time_base = (AVRational){1, in_codecpar->sample_rate}; } - outStream->codecpar->codec_tag = 0; } // Open output file @@ -221,22 +318,58 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio } } + AVDictionary *opts = NULL; + + // Set output format options + av_dict_set(&opts, "movflags", "faststart+frag_keyframe", 0); + av_dict_set(&opts, "brand", "mp42", 0); + // Write output file header - if (avformat_write_header(outputFormatContext, nullptr) < 0) { - fprintf(stderr, "Error occurred when writing header to output file\n"); + res = avformat_write_header(outputFormatContext, &opts); + av_dict_free(&opts); + if (res < 0) { + char errbuf[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + av_strerror(res, errbuf, AV_ERROR_MAX_STRING_SIZE); + fprintf(stderr, "Error occurred when writing header to output file: %s (error code: %d)\n", errbuf, res); avformat_close_input(&inputFormatContext); avformat_free_context(outputFormatContext); return; } +#if 0 // Start a thread to stop the streaming after the specified duration std::thread stop_thread([&]() { std::this_thread::sleep_for(std::chrono::milliseconds(duration)); av_read_pause(inputFormatContext); }); +#endif + + uint32_t framesToSkip = 16; + uint32_t framesSkipped = 0; + // Skip initial frames + while (framesSkipped < framesToSkip) { + if (av_read_frame(inputFormatContext, &packet) < 0) + break; + + if (packet.stream_index == 0) { // Video stream + framesSkipped++; + } + av_packet_unref(&packet); + } + auto startTime = av_gettime(); + // int64_t durationNs = (int64_t)duration * 1000000; + int64_t durationNs = (int64_t)duration * 1000; // Read packets from input and write them to output - while (av_read_frame(inputFormatContext, &packet) >= 0) { + while (1) { + + if ((av_gettime() - startTime) >= durationNs) { + // printf("Duration limit reached (%d seconds)\n", ctx->duration_secs); + break; + } + + +#if 0 AVStream* inStream = inputFormatContext->streams[packet.stream_index]; AVStream* outStream = outputFormatContext->streams[packet.stream_index]; @@ -249,11 +382,14 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio fprintf(stderr, "Error muxing packet\n"); break; } +#endif + if (av_read_frame(inputFormatContext, &packet) < 0) break; + av_write_frame(outputFormatContext, &packet); av_packet_unref(&packet); } - stop_thread.join(); + // stop_thread.join(); // Write output file trailer av_write_trailer(outputFormatContext); diff --git a/app/src/main/cpp/media/RTSPRecorder.h b/app/src/main/cpp/media/RTSPRecorder.h index c406a43e..56d66568 100644 --- a/app/src/main/cpp/media/RTSPRecorder.h +++ b/app/src/main/cpp/media/RTSPRecorder.h @@ -10,7 +10,7 @@ // void dumpRtspToMp4(const std::string &rtspUrl, const std::string &outputPath, uint32_t durationInMs); 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); +void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration, const std::string& userName, const std::string& password, net_handle_t netHandle); class RTSPRecorder { diff --git a/app/src/main/cpp/netcamera/HangYuCtrl.cpp b/app/src/main/cpp/netcamera/HangYuCtrl.cpp index 66200efd..e015bd00 100644 --- a/app/src/main/cpp/netcamera/HangYuCtrl.cpp +++ b/app/src/main/cpp/netcamera/HangYuCtrl.cpp @@ -72,7 +72,7 @@ bool HangYuCtrl::UpdateTime(time_t ts) std::string reqData = ""; - std::string url = "http://" + m_ip + " /System/Time"; + std::string url = "http://" + m_ip + "/System/Time"; std::vector resData; int res = DoPutRequest(url.c_str(), HTTP_AUTH_TYPE_BASIC, m_userName.c_str(), m_password.c_str(), m_netHandle, reqData.c_str(), resData); diff --git a/app/src/main/cpp/netcamera/VendorCtrl.cpp b/app/src/main/cpp/netcamera/VendorCtrl.cpp index 96ee7913..777d29af 100644 --- a/app/src/main/cpp/netcamera/VendorCtrl.cpp +++ b/app/src/main/cpp/netcamera/VendorCtrl.cpp @@ -3,7 +3,7 @@ // #include "VendorCtrl.h" -VendorCtrl::VendorCtrl(const std::string& ip, const std::string& userName, const std::string& password, uint8_t channel, net_handle_t netHandle) : +VendorCtrl::VendorCtrl(const std::string& ip, const std::string& userName, const std::string& password, uint8_t channel, net_handle_t netHandle, bool syncTime/* = true*/) : m_ip(ip), m_userName(userName), m_password(password), m_channel(channel), m_netHandle(netHandle) { } diff --git a/app/src/main/cpp/netcamera/VendorCtrl.h b/app/src/main/cpp/netcamera/VendorCtrl.h index faf4b7ae..052fed9f 100644 --- a/app/src/main/cpp/netcamera/VendorCtrl.h +++ b/app/src/main/cpp/netcamera/VendorCtrl.h @@ -11,7 +11,7 @@ class VendorCtrl { public: - VendorCtrl(const std::string& ip, const std::string& userName, const std::string& password, uint8_t channel, net_handle_t netHandle); + VendorCtrl(const std::string& ip, const std::string& userName, const std::string& password, uint8_t channel, net_handle_t netHandle, bool syncTime = true); virtual ~VendorCtrl() {} virtual bool SetOsd() = 0; diff --git a/app/src/main/cpp/netcamera/YuShiCtrl.cpp b/app/src/main/cpp/netcamera/YuShiCtrl.cpp index 24eeacf7..93ca9d22 100644 --- a/app/src/main/cpp/netcamera/YuShiCtrl.cpp +++ b/app/src/main/cpp/netcamera/YuShiCtrl.cpp @@ -4,6 +4,9 @@ #include "YuShiCtrl.h" #include "httpclient.h" +#include "netcamera.h" + +#include YuShiCtrl::~YuShiCtrl() { @@ -24,6 +27,38 @@ std::string YuShiCtrl::GetStreamingUrl(uint8_t channel) { // /LAPI/V1.0/Channels//Media/Video/Streams//LiveStreamURL?TransType=&TransProtocol= + char url[128] = { 0 }; + snprintf(url, sizeof(url), "http://%s/LAPI/V1.0/Channels/%u/Media/Video/Streams/0/LiveStreamURL", m_ip.c_str(), (uint32_t)channel); + + std::vector resData; + int res = DoGetRequest(url, HTTP_AUTH_TYPE_DIGEST, m_userName.c_str(), m_password.c_str(), m_netHandle, resData); + if (res != 0 || resData.empty()) + { + return ""; + } + + resData.push_back(0); + Json::CharReaderBuilder builder; + std::unique_ptr reader(builder.newCharReader()); + + Json::Value json; + const char* doc = (const char*)&(resData[0]); + if (reader->parse(doc, doc + resData.size() - 1, &json, NULL)) + { + if (json.isMember("Response")) + { + Json::Value& jsonRes = json["Response"]; + if (jsonRes.isMember("Data")) + { + Json::Value& jsonData = jsonRes["Data"]; + if (jsonData.isMember("URL")) + { + return std::string(jsonData["URL"].asCString()); + } + } + } + } + return ""; } @@ -31,14 +66,27 @@ bool YuShiCtrl::UpdateTime(time_t ts) { // /LAPI/V1.0/System/Time +#if 0 Json::Value jsonData(Json::objectValue); jsonData["TimeZone"] = "GMT+08:00"; jsonData["DeviceTime"] = (int64_t)ts; jsonData["DateFormat"] = 0; // YYYY-MM-DD jsonData["HourFormat"] = 1; // 24H +#endif - return false; + std::string contents = "{\"TimeZone\":\"GMT+08:00\",\"DateFormat\":0,\"HourFormat\":1,\"DeviceTime\":" + std::to_string(ts) + "}"; + + std::string url = "http://" + m_ip + "/LAPI/V1.0/System/Time"; + std::vector resData; + int res = DoPutRequest(url.c_str(), HTTP_AUTH_TYPE_BASIC, m_userName.c_str(), m_password.c_str(), m_netHandle, contents.c_str(), resData); + + if (res != 0) + { + return false; + } + + return true; } bool YuShiCtrl::TakePhoto(std::vector& img) diff --git a/app/src/main/cpp/netcamera/httpclient.cpp b/app/src/main/cpp/netcamera/httpclient.cpp index 6f4e4ac4..9d96d52f 100644 --- a/app/src/main/cpp/netcamera/httpclient.cpp +++ b/app/src/main/cpp/netcamera/httpclient.cpp @@ -21,6 +21,7 @@ static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid) static int SockOptCallback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) { + return CURL_SOCKOPT_OK; net_handle_t netHandle = *((net_handle_t *)clientp); int res = android_setsocknetwork(netHandle, curlfd); diff --git a/common/src/main/java/com/xypower/common/FileDownloader.java b/common/src/main/java/com/xypower/common/FileDownloader.java index 56a3d47a..ab0f0626 100644 --- a/common/src/main/java/com/xypower/common/FileDownloader.java +++ b/common/src/main/java/com/xypower/common/FileDownloader.java @@ -38,14 +38,18 @@ public class FileDownloader { connection.setDoInput(true); connection.connect(); final File temp = new File(filePath); - if (temp.exists()) - temp.delete(); - temp.createNewFile(); + if (temp.exists()) { + long fileSize = temp.length(); + connection.setRequestProperty("Range", "bytes=" + Long.toString(fileSize) + "-"); + } + // if (temp.exists()) + // temp.delete(); + // temp.createNewFile(); temp.setReadable(true, false); temp.setWritable(true, false); downloadFile = temp; Log.d("download", "url " + urlString + "\n save to " + temp); - os = new FileOutputStream(temp); + os = new FileOutputStream(temp, true); String encoding = connection.getContentEncoding(); is = connection.getInputStream(); From 28315925ee686fc5733e44de82d1bbe58dfbc6fd Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 18 Mar 2025 19:12:59 +0800 Subject: [PATCH 03/20] =?UTF-8?q?=E4=BF=9D=E5=AD=98yuv=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/camera2/ndkcamera.cpp | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/app/src/main/cpp/camera2/ndkcamera.cpp b/app/src/main/cpp/camera2/ndkcamera.cpp index bb5bda93..450a611a 100644 --- a/app/src/main/cpp/camera2/ndkcamera.cpp +++ b/app/src/main/cpp/camera2/ndkcamera.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,55 @@ #include "DngCreator.h" +void saveYuvToFile(AImage* image, const std::string& filePath) { + + int32_t width, height; + AImage_getWidth(image, &width); + AImage_getHeight(image, &height); + + // 获取 YUV 数据 + uint8_t* yPlane = nullptr; + uint8_t* uPlane = nullptr; + uint8_t* vPlane = nullptr; + int yLength, uLength, vLength; + + AImage_getPlaneData(image, 0, &yPlane, &yLength); // Y 分量 + AImage_getPlaneData(image, 1, &uPlane, &uLength); // U 分量 + AImage_getPlaneData(image, 2, &vPlane, &vLength); // V 分量 + + int32_t yStride, uStride, vStride; + AImage_getPlaneRowStride(image, 0, &yStride); // Y 分量的 Stride + AImage_getPlaneRowStride(image, 1, &uStride); // U 分量的 Stride + AImage_getPlaneRowStride(image, 2, &vStride); // V 分量的 Stride + + + + // 打开文件 + std::ofstream file(filePath, std::ios::binary); + if (!file.is_open()) { + // 文件打开失败 + return; + } + + // 写入 Y 分量(逐行复制,处理 Stride) + for (int i = 0; i < height; i++) { + file.write(reinterpret_cast(yPlane + i * yStride), width); + } + + // 写入 U 分量(逐行复制,处理 Stride) + for (int i = 0; i < height / 2; i++) { + file.write(reinterpret_cast(uPlane + i * uStride), width / 2); + } + + // 写入 V 分量(逐行复制,处理 Stride) + for (int i = 0; i < height / 2; i++) { + file.write(reinterpret_cast(vPlane + i * vStride), width / 2); + } + // 关闭文件 + file.close(); +} + + #ifdef _DEBUG void Auto_AImage_delete(AImage* image) { @@ -1327,6 +1377,16 @@ void NdkCamera::onImageAvailable(AImageReader* reader) int64_t frameTs = 0; mstatus = AImage_getTimestamp(image, &frameTs); + +#ifdef OUTPUT_DBG_INFO + if (mWidth == 1920) + { + std::string dt = FormatLocalDateTime("%d%02d%02d%02d%02d%02d", time(NULL)); + std::string fileName = "/sdcard/com.xypower.mpapp/tmp/" + dt; + fileName += "_" + mCameraId + std::to_string(frameTs) + ".yuv"; + saveYuvToFile(image, fileName.c_str()); + } +#endif AImage_delete(image); bool captureCompleted = false; @@ -1926,6 +1986,8 @@ void NdkCamera::FireOneCapture(uint64_t ts) cv::imwrite(fileName, it->second, params); } } + + #endif onOneCapture(mCharacteristics, mCaptureResults.back(), mFinalLdr, ts - m_startTime, mOneFrame.back().second); } From 2c0b32fe64d5e3cda17e20b12c1bb168b9d251a3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 18 Mar 2025 19:15:49 +0800 Subject: [PATCH 04/20] Update version to 1.3.87 Based Core Version to 1.4.50 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index eadead29..40f0959e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { // 10,00,000 major-minor-build def AppMajorVersion = 1 def AppMinorVersion = 3 -def AppBuildNumber = 86 +def AppBuildNumber = 87 def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber From 955efa53895dedf6a8998ec6554621d31f0cc3b5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 18 Mar 2025 20:02:17 +0800 Subject: [PATCH 05/20] =?UTF-8?q?RTSP=E5=BD=95=E5=88=B6=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/MicroPhoto.cpp | 16 +++++++++++ app/src/main/cpp/PhoneDevice.cpp | 16 +++++++++-- app/src/main/cpp/media/RTSPRecorder.cpp | 35 ++++++++++++++++++++----- app/src/main/cpp/netcamera/VendorCtrl.h | 1 + 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/app/src/main/cpp/MicroPhoto.cpp b/app/src/main/cpp/MicroPhoto.cpp index a7f1a29c..fcf6e6a9 100644 --- a/app/src/main/cpp/MicroPhoto.cpp +++ b/app/src/main/cpp/MicroPhoto.cpp @@ -27,6 +27,12 @@ #include #endif +#ifdef USING_FFMPEG +extern "C" { +#include +} +#endif + #include #include @@ -235,6 +241,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) mosquitto_lib_init(); #endif +#ifdef USING_FFMPEG + // av_register_all(); + avformat_network_init(); +#endif + return result; } @@ -245,6 +256,11 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) #endif curl_global_cleanup(); + +#ifdef USING_FFMPEG + // av_register_all(); + avformat_network_deinit(); +#endif } bool GetJniEnv(JavaVM *vm, JNIEnv **env, bool& didAttachThread) diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 132cbe7c..39ac3718 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1822,7 +1822,19 @@ 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, localPhotoInfo.userName, localPhotoInfo.password, GetEthnetHandle()); + XYLOG(XYLOG_SEVERITY_DEBUG, "Start Recording CH=%u PR=%X PHOTOID=%u", (uint32_t)localPhotoInfo.channel, (unsigned int)localPhotoInfo.preset, localPhotoInfo.photoId); + + if (vendorCtrl->HasAuthOnStreaming()) + { + dumpRtspToMp4(streamingUrl.c_str(), tmpFile.c_str(), localPhotoInfo.duration * 1000, localPhotoInfo.userName, localPhotoInfo.password, GetEthnetHandle()); + } + else + { + dumpRtspToMp4(streamingUrl.c_str(), tmpFile.c_str(), localPhotoInfo.duration * 1000, "", "", GetEthnetHandle()); + } + + XYLOG(XYLOG_SEVERITY_DEBUG, "Stop Recording CH=%u PR=%X PHOTOID=%u", (uint32_t)localPhotoInfo.channel, (unsigned int)localPhotoInfo.preset, localPhotoInfo.photoId); + ethernetPowerCtrl.reset(); XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power OFF"); @@ -1837,7 +1849,7 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c else { TakePhotoCb(0, localPhotoInfo, "", 0); - XYLOG(XYLOG_SEVERITY_ERROR, "Failed to TP on NET Camera CH=%u PR=%X PHOTOID=%u URL=http://%s%s", (uint32_t)localPhotoInfo.channel, (uint32_t)localPhotoInfo.preset, + XYLOG(XYLOG_SEVERITY_ERROR, "Failed to TP on NET Camera CH=%u PR=%X PHOTOID=%u URL=%s", (uint32_t)localPhotoInfo.channel, (uint32_t)localPhotoInfo.preset, localPhotoInfo.photoId, ip, streamingUrl.c_str()); } // Notify to take next photo diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index a7324a34..32706cb8 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -218,14 +218,11 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio AVFormatContext* outputFormatContext = nullptr; AVPacket packet; - av_register_all(); - avformat_network_init(); - #ifndef NDEBUG // Set the custom log callback av_log_set_callback(ffmpeg_log_callback); - av_log_set_level(AV_LOG_TRACE); + av_log_set_level(AV_LOG_WARNING); #endif @@ -270,6 +267,11 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio AVStream* inStream = inputFormatContext->streams[i]; const AVCodecParameters *in_codecpar = inStream->codecpar; + // Skip audio streams + if (inStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + continue; + } + if (in_codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // Copy video stream as-is const AVCodec *codec = avcodec_find_decoder(in_codecpar->codec_id); @@ -359,7 +361,7 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio auto startTime = av_gettime(); // int64_t durationNs = (int64_t)duration * 1000000; - int64_t durationNs = (int64_t)duration * 1000; + int64_t durationNs = (int64_t)(duration + 32) * 1000; // Read packets from input and write them to output while (1) { @@ -385,8 +387,29 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio #endif if (av_read_frame(inputFormatContext, &packet) < 0) break; - av_write_frame(outputFormatContext, &packet); + + // Skip audio packets + if (inputFormatContext->streams[packet.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + { + av_packet_unref(&packet); + continue; + } + + // Adjust packet timebase + AVStream *in_stream = inputFormatContext->streams[packet.stream_index]; + AVStream *out_stream = outputFormatContext->streams[packet.stream_index]; + av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base); + packet.pos = -1; + + res = av_write_frame(outputFormatContext, &packet); + av_packet_unref(&packet); + + if (res < 0) + { + break; + } + } // stop_thread.join(); diff --git a/app/src/main/cpp/netcamera/VendorCtrl.h b/app/src/main/cpp/netcamera/VendorCtrl.h index 052fed9f..84676581 100644 --- a/app/src/main/cpp/netcamera/VendorCtrl.h +++ b/app/src/main/cpp/netcamera/VendorCtrl.h @@ -20,6 +20,7 @@ public: virtual bool UpdateTime(time_t ts) = 0; virtual bool TakePhoto(std::vector& img) = 0; virtual bool TakeVideo(uint32_t duration, std::string path) = 0; + virtual bool HasAuthOnStreaming() const { return false; } protected: From b08adad0a453f541f4397b132173eaa3bc3a2195 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 18 Mar 2025 20:03:03 +0800 Subject: [PATCH 06/20] Update version to 1.3.88 Based Core Version to 1.4.50 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 40f0959e..e4ccc603 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { // 10,00,000 major-minor-build def AppMajorVersion = 1 def AppMinorVersion = 3 -def AppBuildNumber = 87 +def AppBuildNumber = 88 def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber From e74c0185a53d809266fab8b82f894b293121f076 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 19 Mar 2025 11:28:28 +0800 Subject: [PATCH 07/20] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B5=81=E8=BD=AC?= =?UTF-8?q?=E5=8F=91=E7=9A=84=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/media/Streaming.cpp | 95 +++++++++++++++++++++++++++- app/src/main/cpp/media/Streaming.h | 36 ++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/app/src/main/cpp/media/Streaming.cpp b/app/src/main/cpp/media/Streaming.cpp index a0bd9b3a..addb2d45 100644 --- a/app/src/main/cpp/media/Streaming.cpp +++ b/app/src/main/cpp/media/Streaming.cpp @@ -156,4 +156,97 @@ void StreamForwarder::processFrame(AVFrame* frame) { frame->width, frame->height); } } -#endif \ No newline at end of file +#endif + + +bool StreamForwarder::initialize(const std::string& input, const std::string& output) { + inputUrl = input; + outputUrl = output; + + if (avformat_open_input(&inputFormatContext, inputUrl.c_str(), nullptr, nullptr) < 0) { + return false; + } + + if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) { + cleanup(); + return false; + } + + if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mpegts", + outputUrl.c_str()) < 0) { + cleanup(); + return false; + } + + return true; +} + +bool StreamForwarder::start() +{ + if (!inputFormatContext || !outputFormatContext) { + return false; + } + + for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) { + AVStream* inStream = inputFormatContext->streams[i]; + AVStream* outStream = avformat_new_stream(outputFormatContext, + inStream->codec->codec); + + if (!outStream) { + cleanup(); + return false; + } + + if (avcodec_parameters_copy(outStream->codecpar, + inStream->codecpar) < 0) { + cleanup(); + return false; + } + } + + if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&outputFormatContext->pb, outputUrl.c_str(), + AVIO_FLAG_WRITE) < 0) { + cleanup(); + return false; + } + } + + if (avformat_write_header(outputFormatContext, nullptr) < 0) { + cleanup(); + return false; + } + + AVPacket packet; + while (!stopRequested && av_read_frame(inputFormatContext, &packet) >= 0) { + av_write_frame(outputFormatContext, &packet); + av_packet_unref(&packet); + } + + av_write_trailer(outputFormatContext); + return true; +} + +bool StreamForwarder::stop() +{ + stopRequested = true; + return true; +} + +bool StreamForwarder::isStreaming() const +{ + return !stopRequested; +} + +void StreamForwarder::cleanup() { + if (inputFormatContext) { + avformat_close_input(&inputFormatContext); + } + + if (outputFormatContext) { + if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { + avio_closep(&outputFormatContext->pb); + } + avformat_free_context(outputFormatContext); + } +} diff --git a/app/src/main/cpp/media/Streaming.h b/app/src/main/cpp/media/Streaming.h index 9819f40f..e4c1fbc1 100644 --- a/app/src/main/cpp/media/Streaming.h +++ b/app/src/main/cpp/media/Streaming.h @@ -7,6 +7,7 @@ #include #include +#include #include @@ -21,9 +22,12 @@ class Streaming { public: virtual ~Streaming() {} - virtual void start() {} - virtual void stop() {} + virtual bool start() { return false; } + virtual bool stop() { return false; } + virtual bool isStreaming() const { return false; } }; + + #if 0 class StreamForwarder : public Streaming { @@ -44,7 +48,35 @@ private: bool openInput(const std::string& inputUrl); bool openOutput(const std::string& outputUrl); void forwardPackets(); + void setFrameCallback(std::function callback); }; #endif + +class StreamForwarder : public Streaming { +private: + AVFormatContext* inputFormatContext; + AVFormatContext* outputFormatContext; + std::string inputUrl; + std::string outputUrl; + +public: + StreamForwarder() : inputFormatContext(nullptr), outputFormatContext(nullptr) {} + + ~StreamForwarder() { + cleanup(); + } + + bool initialize(const std::string& input, const std::string& output); + virtual bool start(); + virtual bool stop(); + virtual bool isStreaming() const; + +private: + void cleanup(); + + bool stopRequested; +}; + + #endif //MICROPHOTO_STREAMING_H From ebcd0c4dca5f9dcf4c14fe840eaf1786336fd230 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 19 Mar 2025 11:28:46 +0800 Subject: [PATCH 08/20] =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=B5=81=E5=AA=92?= =?UTF-8?q?=E4=BD=93=E8=AE=BF=E9=97=AE=E7=9A=84=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/MicroPhoto.cpp | 22 +++++++++-- .../com/xypower/mpapp/MicroPhotoService.java | 38 +++++++++++++------ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/app/src/main/cpp/MicroPhoto.cpp b/app/src/main/cpp/MicroPhoto.cpp index fcf6e6a9..73e1c3c5 100644 --- a/app/src/main/cpp/MicroPhoto.cpp +++ b/app/src/main/cpp/MicroPhoto.cpp @@ -389,7 +389,7 @@ Java_com_xypower_mpapp_MicroPhotoService_init( extern "C" JNIEXPORT jboolean JNICALL Java_com_xypower_mpapp_MicroPhotoService_notifyToTakePhoto( JNIEnv* env, - jobject pThis, jlong handler, jint channel, jint preset, jlong scheduleTime, jboolean photoOrVideo) { + jobject pThis, jlong handler, jint channel, jint preset, jlong scheduleTime, jstring url, jint mediaType) { if (channel < 0 || channel > 0xFFFF) { @@ -401,12 +401,27 @@ Java_com_xypower_mpapp_MicroPhotoService_notifyToTakePhoto( return JNI_FALSE; } - unsigned char type = photoOrVideo ? 0 : 1; + uint8_t type = (uint8_t)mediaType; // std::thread th(&Runner::RequestCapture, pTerminal, (unsigned int)channel, (unsigned int)preset, type, (uint64_t)scheduleTime, 0, true); // th.detach(); if (channel < 0x100) { - pTerminal->RequestCapture((uint32_t)channel, (unsigned int)preset, type, (uint64_t)scheduleTime, 0, true); + if (mediaType == XY_MEDIA_TYPE_PHOTO || mediaType == XY_MEDIA_TYPE_VIDEO) + { + pTerminal->RequestCapture((uint32_t)channel, (unsigned int)preset, type, (uint64_t)scheduleTime, 0, true); + } + else if (mediaType == XY_MEDIA_TYPE_STREAM) + { + // virtual bool StartStream(unsigned char channel, unsigned char preset, const std::string& url, uint32_t* photoId = NULL); + // virtual bool StopStream(unsigned char channel, unsigned char preset, uint32_t photoId); + uint32_t photoId = 0; + std::string urlStr = jstring2string(env, url); + pTerminal->StartStream(channel, preset, urlStr, &photoId); + } + else if (mediaType == XY_MEDIA_TYPE_STREAM_OFF) + { + pTerminal->StopStream(channel, preset, 0); + } } else { @@ -418,7 +433,6 @@ Java_com_xypower_mpapp_MicroPhotoService_notifyToTakePhoto( return JNI_TRUE; } - extern "C" JNIEXPORT jlong JNICALL Java_com_xypower_mpapp_MicroPhotoService_takePhoto( JNIEnv* env, diff --git a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java index 0e3a29ac..3ea2b50a 100644 --- a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java +++ b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java @@ -98,10 +98,17 @@ public class MicroPhotoService extends Service { public static final int MSG_WHAT_LOG = 10; - public final static int MSG_WHAT_SENDING_HB = 40; - public final static int MSG_WHAT_MAX = 1000; + public final static int MEDIA_TYPE_PHOTO = 0; + public final static int MEDIA_TYPE_VIDEO = 1; + + public final static int MEDIA_TYPE_LOG = 2; + + public final static int MEDIA_TYPE_STREAMING = 0x10; + public final static int MEDIA_TYPE_STREAMING_OFF = 0x11; + + public final static int BROADCAST_REQUEST_CODE_HEARTBEAT = 1; public final static int BROADCAST_REQUEST_CODE_TAKING_PHOTO = 2; public final static int BROADCAST_REQUEST_CODE_GPS = 3; @@ -127,14 +134,15 @@ public class MicroPhotoService extends Service { private static final String EXTRA_PARAM_CHANNEL = "Channel"; private static final String EXTRA_PARAM_PRESET = "Preset"; private static final String EXTRA_PARAM_PHOTO_OR_VIDEO = "PhotoOrVideo"; + + private static final String EXTRA_PARAM_MEDIA_TYPE = "mediaType"; + + private static final String EXTRA_PARAM_URL = "url"; private static final String EXTRA_PARAM_SCHEDULES = "Schedules"; private static final String EXTRA_PARAM_SCHEDULE = "Schedule_"; private static final String EXTRA_PARAM_TAKING_TIME = "TakingTime"; private static final String EXTRA_PARAM_TIME = "Time"; - // private static final String EXTRA_PARAM_TIMER_UID = "TimerUid"; - // private static final String EXTRA_PARAM_TIMEOUT = "Timeout"; - // private static final String EXTRA_PARAM_TIMES = "Times"; - // private static final String EXTRA_PARAM_ELASPED_TIMES = "ElapsedTimes"; + private static final String FOREGROUND_CHANNEL_ID = "fg_mpapp"; public static class STATE_SERVICE { public static final int CONNECTED = 10; @@ -513,7 +521,7 @@ public class MicroPhotoService extends Service { int channel = (int) ((val & 0xFFFF000L) >> 12); int preset = (int) ((val & 0xFF0L) >> 4); - boolean photoOrVideo = ((val & 0xFL) == 0); + int mediaType = (int)(val & 0xFL); if (channel >= 256) { @@ -523,7 +531,7 @@ public class MicroPhotoService extends Service { { infoLog("IMG Timer Fired: CH=" + channel + " PR=" + preset); } - mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, photoOrVideo); + mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, ts, null, mediaType); } } @@ -536,12 +544,20 @@ public class MicroPhotoService extends Service { } else if (TextUtils.equals(ACTION_TAKE_PHOTO_MANUALLY, action)) { int channel = intent.getIntExtra(EXTRA_PARAM_CHANNEL, 0); int preset = intent.getIntExtra(EXTRA_PARAM_PRESET, 0xFF); + String url = intent.getStringExtra(EXTRA_PARAM_URL); // long ts = intent.getLongExtra(EXTRA_PARAM_TIME, 0); - boolean photoOrVideo = intent.getBooleanExtra(EXTRA_PARAM_PHOTO_OR_VIDEO, true); + + int mediaType = 0; + if (intent.hasExtra(EXTRA_PARAM_MEDIA_TYPE)) { + mediaType = intent.getIntExtra(EXTRA_PARAM_MEDIA_TYPE, 0); + } else if (intent.hasExtra(EXTRA_PARAM_PHOTO_OR_VIDEO)) { + boolean photoOrVideo = intent.getBooleanExtra(EXTRA_PARAM_PHOTO_OR_VIDEO, true); + mediaType = photoOrVideo ? 0 : 1; + } long ts = System.currentTimeMillis() / 1000; Log.i(TAG, "Take Photo CH=" + channel + " PR=" + preset + " Mannually"); - mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, 0, photoOrVideo); + mService.notifyToTakePhoto(mService.mNativeHandle, channel, preset, 0, url, mediaType); } else if (TextUtils.equals(ACTION_UPDATE_CONFIGS, action)) { int restart = intent.getIntExtra("restart", 0); Log.i(TAG, "UPD CFG Fired ACTION=" + action + " restart=" + restart); @@ -1605,7 +1621,7 @@ cellSignalStrengthGsm.getDbm(); protected native long[] getPhotoTimeData(long handler, long startTime); protected native long[] getPhotoTimeData2(long handler); // protected native long[] getNextScheduleItem(long handler); - protected native boolean notifyToTakePhoto(long handler, int channel, int preset, long scheduleTime, boolean photoOrVideo); + protected native boolean notifyToTakePhoto(long handler, int channel, int preset, long scheduleTime, String url, int mediaType); protected native boolean sendHeartbeat(long handler, int signalLevel); protected native boolean reloadConfigs(long handler); From 69a3997805e161de2f5c5264745d2059a664ef8f Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 19 Mar 2025 16:05:43 +0800 Subject: [PATCH 09/20] =?UTF-8?q?=E5=A2=9E=E5=8A=A0rtsp=20=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/PhoneDevice.cpp | 115 +++++++++++++++--------- app/src/main/cpp/PhoneDevice.h | 9 +- app/src/main/cpp/media/RTSPRecorder.cpp | 15 ++-- app/src/main/cpp/netcamera/HangYuCtrl.h | 1 + 4 files changed, 93 insertions(+), 47 deletions(-) diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 39ac3718..26f01981 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1763,7 +1763,7 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c if (netHandle == 0) { // Wait about 10s - for (int idx = 0; idx < 84; idx++) + for (int idx = 0; idx < 128; idx++) { std::this_thread::sleep_for(std::chrono::milliseconds(128)); netHandle = GetEthnetHandle(); @@ -1792,12 +1792,7 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c // SetStaticIp(); std::this_thread::sleep_for(std::chrono::milliseconds(256)); - struct in_addr addr; - char ip[32] = { 0 }; - addr.s_addr = localPhotoInfo.ip; - strcpy(ip, inet_ntoa(addr)); - // strcpy(netPhotoInfo.outputPath, path.c_str()); - + std::string ip = GetIpStr(localPhotoInfo.ip); VendorCtrl* vendorCtrl = MakeVendorCtrl(localPhotoInfo.vendor, localPhotoInfo.channel, ip, localPhotoInfo.userName, localPhotoInfo.password, netHandle); if (vendorCtrl == NULL) { @@ -1864,62 +1859,98 @@ bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c 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()) + time_t ts = time(NULL); + uint32_t waitTime = photoInfo.selfTestingTime; + if(!GpioControl::GetSelftestStatus(waitTime)) { - it->second->stop(); - it->second.reset(); - m_streamings.erase(it); + m_isSelfTesting.store(true); + waitTime = (waitTime != 0) ? (waitTime * 1024) : 10240; + std::this_thread::sleep_for(std::chrono::milliseconds(waitTime)); + m_isSelfTesting.store(false); } - NET_PHOTO_INFO netPhotoInfo = { 0, 0 }; - if (photoInfo.vendor == 1) + XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power ON"); + std::shared_ptr ethernetPowerCtrl = std::make_shared(1); + // std::shared_ptr ethernetPowerCtrl; + + net_handle_t netHandle = GetEthnetHandle(); + if (netHandle == 0) { - // Hai Kang - netPhotoInfo.authType = HTTP_AUTH_TYPE_DIGEST; - snprintf(netPhotoInfo.url, sizeof(netPhotoInfo.url), "/ISAPI/Streaming/channels/1/picture?"); + // Wait about 10s + for (int idx = 0; idx < 84; idx++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(128)); + netHandle = GetEthnetHandle(); + + if (netHandle != 0) + { + break; + } + } } - else if (photoInfo.vendor == 2) + + if (netHandle == 0) { - // Hang Yu - strcpy(netPhotoInfo.url, "/cgi-bin/snapshot.cgi"); + // timeout + XYLOG(XYLOG_SEVERITY_ERROR, "Ethernet not existing CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.preset, photoInfo.photoId); +#ifdef NDEBUG + TakePhotoCb(0, localPhotoInfo, "", 0); + return false; +#endif } - else if (photoInfo.vendor == 3) + else { - // 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"); + XYLOG(XYLOG_SEVERITY_INFO, "Ethernet is Available CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.preset, photoInfo.photoId); } - else if (photoInfo.vendor == 5) + + // SetStaticIp(); + std::this_thread::sleep_for(std::chrono::milliseconds(256)); + + std::map::iterator it = m_streamings.find(photoInfo.channel); + if (it != m_streamings.end()) { - // 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); + it->second.stream->stop(); + it->second.stream.reset(); + it->second.powerCtrl.reset(); + it->second.ethernetPowerCtrl.reset(); + m_streamings.erase(it); } - else + + std::string ip = GetIpStr(photoInfo.ip); + VendorCtrl* vendorCtrl = MakeVendorCtrl(photoInfo.vendor, photoInfo.channel, ip, photoInfo.userName, photoInfo.password, netHandle); + if (vendorCtrl == NULL) { 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; } + std::string streamingUrl = vendorCtrl->GetStreamingUrl(photoInfo.cameraId); + + if (streamingUrl.empty()) + { + XYLOG(XYLOG_SEVERITY_ERROR, "Invalid Streaming URL CH=%u PR=%X PHOTOID=%u", (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)) { + if (!forwarder->initialize(streamingUrl, url)) { std::cerr << "Failed to initialize stream forwarder" << std::endl; + delete forwarder; return -1; } + STREAMING_CONTEXT ctx; + ctx.stream = std::shared_ptr((Streaming*)forwarder); + ctx.powerCtrl = powerCtrlPtr; + ctx.ethernetPowerCtrl = ethernetPowerCtrl; + m_streamings[photoInfo.channel] = ctx; + // Optional: Set callback to process video frames #if 0 forwarder->setFrameCallback([](uint8_t* data, int linesize, int width, int height) { @@ -1942,12 +1973,14 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: auto it = m_streamings.find(photoInfo.channel); if (it != m_streamings.end()) { - it->second->stop(); - it->second.reset(); + it->second.stream->stop(); + it->second.stream.reset(); + it->second.powerCtrl.reset(); + it->second.ethernetPowerCtrl.reset(); m_streamings.erase(it); } } -#endif + return true; } diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index 5c4b330f..fd6206e6 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -157,6 +157,13 @@ class PowerControl; class VendorCtrl; class Streaming; +struct STREAMING_CONTEXT +{ + std::shared_ptr stream; + std::shared_ptr powerCtrl; + std::shared_ptr ethernetPowerCtrl; +}; + class CPhoneDevice : public IDevice { public: @@ -428,7 +435,7 @@ protected: std::atomic m_collecting; unsigned long long localDelayTime; - std::map > m_streamings; + std::map m_streamings; }; diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index 32706cb8..c06d69e8 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -222,22 +222,27 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio // Set the custom log callback av_log_set_callback(ffmpeg_log_callback); - av_log_set_level(AV_LOG_WARNING); + av_log_set_level(AV_LOG_VERBOSE); #endif + std::string url = rtspUrl; AVDictionary* options = NULL; av_dict_set(&options, "rtsp_transport", "tcp", 0); av_dict_set(&options, "stimeout", "5000000", 0); if (!userName.empty()) { - // av_dict_set(&options, "rtsp_user", userName.c_str(), 0); // Replace with actual username - // av_dict_set(&options, "rtsp_password", password.c_str(), 0); // Replace with actual password - } + av_dict_set(&options, "username", userName.c_str(), 0); // Replace with actual username + av_dict_set(&options, "password", password.c_str(), 0); // Replace with actual password + + char auth[512] = { 0 }; + snprintf(auth, sizeof(auth), "%s:%s@", userName.c_str(), password.c_str()); + url.insert(url.begin() + 7, auth, auth + strlen(auth)); + } // Open input RTSP stream - int res = avformat_open_input(&inputFormatContext, rtspUrl, nullptr, &options); + int res = avformat_open_input(&inputFormatContext, url.c_str(), nullptr, &options); av_dict_free(&options); if (res != 0) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; diff --git a/app/src/main/cpp/netcamera/HangYuCtrl.h b/app/src/main/cpp/netcamera/HangYuCtrl.h index 3cb86ede..4096fb45 100644 --- a/app/src/main/cpp/netcamera/HangYuCtrl.h +++ b/app/src/main/cpp/netcamera/HangYuCtrl.h @@ -19,6 +19,7 @@ public: virtual bool UpdateTime(time_t ts); virtual bool TakePhoto(std::vector& img); virtual bool TakeVideo(uint32_t duration, std::string path); + virtual bool HasAuthOnStreaming() const { return true; } private: From 238f0aeb4fc7c9a40c39fcf6c33e739c061bfc0b Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 19 Mar 2025 16:13:46 +0800 Subject: [PATCH 10/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/media/RTSPRecorder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index c06d69e8..4b6f2068 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -234,7 +234,7 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio { av_dict_set(&options, "username", userName.c_str(), 0); // Replace with actual username av_dict_set(&options, "password", password.c_str(), 0); // Replace with actual password - + char auth[512] = { 0 }; snprintf(auth, sizeof(auth), "%s:%s@", userName.c_str(), password.c_str()); From 120d7fdde7a3ee8a3172e4a188c3cdb72da6b18f Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:34:18 +0800 Subject: [PATCH 11/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/media/RTSPRecorder.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/cpp/media/RTSPRecorder.cpp b/app/src/main/cpp/media/RTSPRecorder.cpp index 4b6f2068..71b40115 100644 --- a/app/src/main/cpp/media/RTSPRecorder.cpp +++ b/app/src/main/cpp/media/RTSPRecorder.cpp @@ -113,9 +113,6 @@ void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duratio AVFormatContext* outputFormatContext = nullptr; AVPacket packet; - 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); @@ -222,7 +219,7 @@ void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duratio // Set the custom log callback av_log_set_callback(ffmpeg_log_callback); - av_log_set_level(AV_LOG_VERBOSE); + av_log_set_level(AV_LOG_WARNING); #endif From 5d312ed1f04dc0830ed7c1e9a3bc7c397c1c9b5a Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:34:39 +0800 Subject: [PATCH 12/20] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/media/Streaming.cpp | 252 ++++++++++++++++++++------- app/src/main/cpp/media/Streaming.h | 34 ++-- 2 files changed, 214 insertions(+), 72 deletions(-) diff --git a/app/src/main/cpp/media/Streaming.cpp b/app/src/main/cpp/media/Streaming.cpp index addb2d45..e14643a7 100644 --- a/app/src/main/cpp/media/Streaming.cpp +++ b/app/src/main/cpp/media/Streaming.cpp @@ -4,12 +4,27 @@ #include "Streaming.h" +#include +#include +#include +#include + #include #include #include #include #include +extern "C" { +#include +#include +#include +#include +#include +} + +extern void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl); + #if 0 StreamForwarder::~StreamForwarder() { stop(); @@ -159,94 +174,213 @@ void StreamForwarder::processFrame(AVFrame* frame) { #endif -bool StreamForwarder::initialize(const std::string& input, const std::string& output) { - inputUrl = input; - outputUrl = output; +RtspForwarder::RtspForwarder(const std::string& input, const std::string& output) + : inputUrl(input), outputUrl(output), isRunning(false) +{ +} - if (avformat_open_input(&inputFormatContext, inputUrl.c_str(), nullptr, nullptr) < 0) { - return false; +bool RtspForwarder::isStreaming() const +{ + return isRunning; +} + +bool RtspForwarder::start() +{ + run(); + return true; +} + +bool RtspForwarder::stop() +{ + isRunning = false; + return true; +} + +int RtspForwarder::run() +{ + isRunning = true; + AVFormatContext* inputFormatContext = nullptr; + AVFormatContext* outputFormatContext = nullptr; + int ret; + int videoStreamIndex = -1; + int64_t startTime = AV_NOPTS_VALUE; + + std::string url = inputUrl; + if (!m_userName.empty()) + { + char auth[512] = { 0 }; + snprintf(auth, sizeof(auth), "%s:%s@", m_userName.c_str(), m_password.c_str()); + + url.insert(url.begin() + 7, auth, auth + strlen(auth)); } - if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) { - cleanup(); - return false; + // Input options + AVDictionary* inputOptions = nullptr; + av_dict_set(&inputOptions, "rtsp_transport", "tcp", 0); + av_dict_set(&inputOptions, "stimeout", "5000000", 0); // 5 second timeout + av_dict_set(&inputOptions, "buffer_size", "1024000", 0); // 1MB buffer + + // Output options + AVDictionary* outputOptions = nullptr; + av_dict_set(&outputOptions, "rtsp_transport", "tcp", 0); + av_dict_set(&outputOptions, "f", "rtsp", 0); + + std::cout << "Opening input: " << url << std::endl; + + // Open input + ret = avformat_open_input(&inputFormatContext, url.c_str(), nullptr, &inputOptions); + if (ret < 0) { + std::cerr << "Could not open input: " << av_err2str(ret) << std::endl; + return ret; } - if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mpegts", - outputUrl.c_str()) < 0) { - cleanup(); - return false; + // Get stream info + ret = avformat_find_stream_info(inputFormatContext, nullptr); + if (ret < 0) { + // std::cerr << "Failed to get stream info: " << av_err2str(ret) << std::endl; + avformat_close_input(&inputFormatContext); + return ret; } - return true; -} + // Find video stream + for (unsigned i = 0; i < inputFormatContext->nb_streams; i++) { + if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + videoStreamIndex = i; + break; + } + } -bool StreamForwarder::start() -{ - if (!inputFormatContext || !outputFormatContext) { - return false; + if (videoStreamIndex == -1) { + // std::cerr << "No video stream found" << std::endl; + avformat_close_input(&inputFormatContext); + return -1; } - for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) { + // Allocate output context + avformat_alloc_output_context2(&outputFormatContext, nullptr, "rtsp", outputUrl.c_str()); + if (!outputFormatContext) { + std::cerr << "Could not create output context" << std::endl; + avformat_close_input(&inputFormatContext); + return AVERROR_UNKNOWN; + } + + // Create output streams by copying from input + for (unsigned i = 0; i < inputFormatContext->nb_streams; i++) { AVStream* inStream = inputFormatContext->streams[i]; - AVStream* outStream = avformat_new_stream(outputFormatContext, - inStream->codec->codec); + AVCodecParameters* inCodecpar = inStream->codecpar; + AVStream* outStream = avformat_new_stream(outputFormatContext, nullptr); if (!outStream) { - cleanup(); - return false; + std::cerr << "Failed to allocate output stream" << std::endl; + avformat_close_input(&inputFormatContext); + avformat_free_context(outputFormatContext); + return AVERROR_UNKNOWN; } - if (avcodec_parameters_copy(outStream->codecpar, - inStream->codecpar) < 0) { - cleanup(); - return false; + ret = avcodec_parameters_copy(outStream->codecpar, inCodecpar); + if (ret < 0) { + std::cerr << "Failed to copy codec parameters" << std::endl; + avformat_close_input(&inputFormatContext); + avformat_free_context(outputFormatContext); + return ret; } + + // Fix codec tag + outStream->codecpar->codec_tag = 0; + + // Copy time base + outStream->time_base = inStream->time_base; } + // Open output if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { - if (avio_open(&outputFormatContext->pb, outputUrl.c_str(), - AVIO_FLAG_WRITE) < 0) { - cleanup(); - return false; + ret = avio_open(&outputFormatContext->pb, outputUrl.c_str(), AVIO_FLAG_WRITE); + if (ret < 0) { + std::cerr << "Could not open output URL: " << av_err2str(ret) << std::endl; + avformat_close_input(&inputFormatContext); + avformat_free_context(outputFormatContext); + return ret; } } - if (avformat_write_header(outputFormatContext, nullptr) < 0) { - cleanup(); - return false; + // Write header + ret = avformat_write_header(outputFormatContext, &outputOptions); + if (ret < 0) { + std::cerr << "Error writing header: " << av_err2str(ret) << std::endl; + avformat_close_input(&inputFormatContext); + if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) + avio_closep(&outputFormatContext->pb); + avformat_free_context(outputFormatContext); + return ret; } + // Main loop - read and write packets AVPacket packet; - while (!stopRequested && av_read_frame(inputFormatContext, &packet) >= 0) { - av_write_frame(outputFormatContext, &packet); - av_packet_unref(&packet); - } - - av_write_trailer(outputFormatContext); - return true; -} + while (isRunning) { + ret = av_read_frame(inputFormatContext, &packet); + if (ret < 0) { + if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + std::cerr << "End of stream or timeout, reconnecting in " + << reconnectDelayMs << "ms" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(reconnectDelayMs)); + avformat_close_input(&inputFormatContext); + ret = avformat_open_input(&inputFormatContext, inputUrl.c_str(), nullptr, &inputOptions); + if (ret < 0) continue; + ret = avformat_find_stream_info(inputFormatContext, nullptr); + if (ret < 0) continue; + continue; + } + break; + } -bool StreamForwarder::stop() -{ - stopRequested = true; - return true; -} + // Fix timestamps if enabled + if (fixTimestamps) { + // Handle timestamp issues similar to FFmpeg warning + AVStream* inStream = inputFormatContext->streams[packet.stream_index]; + AVStream* outStream = outputFormatContext->streams[packet.stream_index]; -bool StreamForwarder::isStreaming() const -{ - return !stopRequested; -} + if (packet.pts == AV_NOPTS_VALUE) { + // Generate PTS if missing + if (startTime == AV_NOPTS_VALUE) { + startTime = av_gettime(); + } + packet.pts = av_rescale_q(av_gettime() - startTime, + AV_TIME_BASE_Q, + inStream->time_base); + packet.dts = packet.pts; + } -void StreamForwarder::cleanup() { - if (inputFormatContext) { - avformat_close_input(&inputFormatContext); - } + // Rescale timestamps to output timebase + packet.pts = av_rescale_q_rnd(packet.pts, + inStream->time_base, + outStream->time_base, + static_cast(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); + packet.dts = av_rescale_q_rnd(packet.dts, + inStream->time_base, + outStream->time_base, + static_cast(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); + packet.duration = av_rescale_q(packet.duration, + inStream->time_base, + outStream->time_base); + } - if (outputFormatContext) { - if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { - avio_closep(&outputFormatContext->pb); + // Write packet to output + ret = av_interleaved_write_frame(outputFormatContext, &packet); + av_packet_unref(&packet); + if (ret < 0) { + std::cerr << "Error writing frame: " << av_err2str(ret) << std::endl; + break; } - avformat_free_context(outputFormatContext); } + + // Write trailer + av_write_trailer(outputFormatContext); + + // Cleanup + avformat_close_input(&inputFormatContext); + if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) + avio_closep(&outputFormatContext->pb); + avformat_free_context(outputFormatContext); + + return 0; } diff --git a/app/src/main/cpp/media/Streaming.h b/app/src/main/cpp/media/Streaming.h index e4c1fbc1..2e5f6884 100644 --- a/app/src/main/cpp/media/Streaming.h +++ b/app/src/main/cpp/media/Streaming.h @@ -8,6 +8,10 @@ #include #include #include +#include +#include +#include + #include @@ -25,6 +29,15 @@ public: virtual bool start() { return false; } virtual bool stop() { return false; } virtual bool isStreaming() const { return false; } + + void setAuth(const std::string& userName, const std::string& password) + { + m_userName = userName; + m_password = password; + } +protected: + std::string m_userName; + std::string m_password; }; @@ -53,30 +66,25 @@ private: #endif -class StreamForwarder : public Streaming { +class RtspForwarder : public Streaming { private: - AVFormatContext* inputFormatContext; - AVFormatContext* outputFormatContext; std::string inputUrl; std::string outputUrl; + std::atomic isRunning; -public: - StreamForwarder() : inputFormatContext(nullptr), outputFormatContext(nullptr) {} + // Options + int reconnectDelayMs = 5000; + bool fixTimestamps = true; - ~StreamForwarder() { - cleanup(); - } +public: + RtspForwarder(const std::string& input, const std::string& output); - bool initialize(const std::string& input, const std::string& output); virtual bool start(); virtual bool stop(); virtual bool isStreaming() const; -private: - void cleanup(); - bool stopRequested; + int run(); }; - #endif //MICROPHOTO_STREAMING_H From ccc0e0e3348e4d2d8008487a9748ed538d20aec5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:35:33 +0800 Subject: [PATCH 13/20] =?UTF-8?q?=E5=8F=82=E6=95=B0=E5=90=8D=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/xypower/mpapp/MicroPhotoService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java index 3ea2b50a..bf0634b3 100644 --- a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java +++ b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java @@ -135,15 +135,16 @@ public class MicroPhotoService extends Service { private static final String EXTRA_PARAM_PRESET = "Preset"; private static final String EXTRA_PARAM_PHOTO_OR_VIDEO = "PhotoOrVideo"; - private static final String EXTRA_PARAM_MEDIA_TYPE = "mediaType"; + private static final String EXTRA_PARAM_MEDIA_TYPE = "MediaType"; - private static final String EXTRA_PARAM_URL = "url"; + private static final String EXTRA_PARAM_URL = "Url"; private static final String EXTRA_PARAM_SCHEDULES = "Schedules"; private static final String EXTRA_PARAM_SCHEDULE = "Schedule_"; private static final String EXTRA_PARAM_TAKING_TIME = "TakingTime"; private static final String EXTRA_PARAM_TIME = "Time"; private static final String FOREGROUND_CHANNEL_ID = "fg_mpapp"; + public static class STATE_SERVICE { public static final int CONNECTED = 10; public static final int NOT_CONNECTED = 0; From c8966009b85d37c015d58cba491df5a319e44f46 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:35:46 +0800 Subject: [PATCH 14/20] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/PhoneDevice.cpp | 24 +++++++++++++++-------- app/src/main/cpp/netcamera/HangYuCtrl.cpp | 10 +++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 26f01981..a3b7ff1b 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1936,13 +1936,12 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: return false; } - StreamForwarder* forwarder = new StreamForwarder(); + RtspForwarder* forwarder = new RtspForwarder(streamingUrl, url); - // Initialize with RTSP input and RTMP output - if (!forwarder->initialize(streamingUrl, url)) { - std::cerr << "Failed to initialize stream forwarder" << std::endl; - delete forwarder; - return -1; + bool res = false; + if (vendorCtrl->HasAuthOnStreaming()) + { + forwarder->setAuth(photoInfo.userName, photoInfo.password); } STREAMING_CONTEXT ctx; @@ -1960,8 +1959,16 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: #endif // Start forwarding - forwarder->start(); - + res = forwarder->start(); +#if 0 + // Initialize with RTSP input and RTMP output + if (!res) + { + XYLOG(XYLOG_SEVERITY_ERROR, "TP: Failed to open stream: %s (%u/%02X) PHOTOID=%u", streamingUrl.c_str(), (unsigned int)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId); + delete forwarder; + return -1; + } +#endif // Wait for user input to stop // std::cout << "Press Enter to stop streaming..." << std::endl; // std::cin.get(); @@ -2284,6 +2291,7 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector< std::thread t([localPhotoInfo, path, pThis, osds, powerCtrlPtr]() mutable { + pThis->TakePhotoCb(1, localPhotoInfo, "", 0); pThis->StartPushStreaming(localPhotoInfo, path, osds, powerCtrlPtr); }); diff --git a/app/src/main/cpp/netcamera/HangYuCtrl.cpp b/app/src/main/cpp/netcamera/HangYuCtrl.cpp index e015bd00..d6fca518 100644 --- a/app/src/main/cpp/netcamera/HangYuCtrl.cpp +++ b/app/src/main/cpp/netcamera/HangYuCtrl.cpp @@ -32,7 +32,15 @@ std::string HangYuCtrl::GetStreamingUrl(uint8_t channel) std::vector resData; - int res = DoGetRequest(url, HTTP_AUTH_TYPE_BASIC, m_userName.c_str(), m_password.c_str(), m_netHandle, resData); + int res = 0; + for (int idx = 0; idx < 10; idx++) + { + res = DoGetRequest(url, HTTP_AUTH_TYPE_BASIC, m_userName.c_str(), m_password.c_str(), m_netHandle, resData); + if (res == 0 && !resData.empty()) + { + break; + } + } if (res != 0 || resData.empty()) { return ""; From ca7e8e5acf49330f023e3f7df535edf38356a26d Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:46:32 +0800 Subject: [PATCH 15/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/PhoneDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index a3b7ff1b..dcce7e27 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1896,7 +1896,7 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: // timeout XYLOG(XYLOG_SEVERITY_ERROR, "Ethernet not existing CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.channel, (uint32_t)photoInfo.preset, photoInfo.photoId); #ifdef NDEBUG - TakePhotoCb(0, localPhotoInfo, "", 0); + TakePhotoCb(0, photoInfo, "", 0); return false; #endif } From 3dbb79e94ca112b8236293a5b2778ea82f207330 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 09:47:10 +0800 Subject: [PATCH 16/20] Update version to 1.3.89 Based Core Version to 1.4.51 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e4ccc603..93cd3ad7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { // 10,00,000 major-minor-build def AppMajorVersion = 1 def AppMinorVersion = 3 -def AppBuildNumber = 88 +def AppBuildNumber = 89 def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber From 32698886d053fddd0b08a0ec7d8be771447ffa12 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 13:32:12 +0800 Subject: [PATCH 17/20] =?UTF-8?q?=E4=BB=8D=E7=84=B6=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=89=88=E6=9C=AC=E7=9A=84=E5=9B=BA=E4=BB=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/netcamera/httpclient.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/cpp/netcamera/httpclient.cpp b/app/src/main/cpp/netcamera/httpclient.cpp index 9d96d52f..6f4e4ac4 100644 --- a/app/src/main/cpp/netcamera/httpclient.cpp +++ b/app/src/main/cpp/netcamera/httpclient.cpp @@ -21,7 +21,6 @@ static size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid) static int SockOptCallback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) { - return CURL_SOCKOPT_OK; net_handle_t netHandle = *((net_handle_t *)clientp); int res = android_setsocknetwork(netHandle, curlfd); From 21fd57061c09407166098ee8a60f0afd6a61202c Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 13:33:52 +0800 Subject: [PATCH 18/20] Update version to 1.3.90 Based Core Version to 1.4.51 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 93cd3ad7..6feb14f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { // 10,00,000 major-minor-build def AppMajorVersion = 1 def AppMinorVersion = 3 -def AppBuildNumber = 89 +def AppBuildNumber = 90 def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber From 808271c7f1b4617b1c36a8ac77cb874da6de8bdb Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 18:12:09 +0800 Subject: [PATCH 19/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/GPIOControl.h | 2 +- app/src/main/cpp/PhoneDevice.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/GPIOControl.h b/app/src/main/cpp/GPIOControl.h index b5d37b2d..eb207b47 100644 --- a/app/src/main/cpp/GPIOControl.h +++ b/app/src/main/cpp/GPIOControl.h @@ -48,7 +48,7 @@ #define CMD_SET_12V_EN_STATE 133 #if 1 #define CMD_SET_SPI_POWER 129 -#define CMD_SET_3V3_PWR_EN 132 +#define CMD_SET_3V3_PWR_EN 2 #endif #define CMD_GET_CAMERA_STATUS 310 diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index dcce7e27..e30c78e9 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -1958,6 +1958,7 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: }); #endif + XYLOG(XYLOG_SEVERITY_INFO, "Start Streaming CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId); // Start forwarding res = forwarder->start(); #if 0 @@ -1977,6 +1978,8 @@ bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& photoInfo, const std: } else if (photoInfo.mediaType == XY_MEDIA_TYPE_STREAM_OFF) { + XYLOG(XYLOG_SEVERITY_INFO, "Stop Streaming CH=%u PR=%X PHOTOID=%u", (uint32_t)photoInfo.channel, (unsigned int)photoInfo.preset, photoInfo.photoId); + auto it = m_streamings.find(photoInfo.channel); if (it != m_streamings.end()) { From b18b9f54e4538f6ad63ed828fcdb59e5a252f408 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 20 Mar 2025 20:49:53 +0800 Subject: [PATCH 20/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=AF=E6=94=B9?= =?UTF-8?q?=E7=9A=84=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/GPIOControl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/cpp/GPIOControl.h b/app/src/main/cpp/GPIOControl.h index eb207b47..b5d37b2d 100644 --- a/app/src/main/cpp/GPIOControl.h +++ b/app/src/main/cpp/GPIOControl.h @@ -48,7 +48,7 @@ #define CMD_SET_12V_EN_STATE 133 #if 1 #define CMD_SET_SPI_POWER 129 -#define CMD_SET_3V3_PWR_EN 2 +#define CMD_SET_3V3_PWR_EN 132 #endif #define CMD_GET_CAMERA_STATUS 310