流媒体实现

ptz
Matthew 3 months ago
parent a38f473ce8
commit ee68539dde

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -86,6 +86,7 @@
<intent>
<action android:name="android.media.action.STILL_IMAGE_CAMERA" />
</intent>
<package android:name="com.xypower.mplive" />
</queries>
<application

@ -21,7 +21,7 @@ if(ANDROID_ABI STREQUAL "armeabi-v7a")
add_definitions(-DUSING_N938)
elseif(ANDROID_ABI STREQUAL "arm64-v8a")
# add_definitions(-DUSING_N938)
#add_definitions(-DUSING_PLZ)
add_definitions(-DUSING_PLZ)
endif()
# OUTPUT_DBG_INFO:
@ -146,6 +146,16 @@ include_directories(hdrplus2/${ANDROID_ABI})
include_directories(${HALIDE_ROOT}/${ANDROID_ABI}/include)
SET(ZLMEDIAKIT_LIBS "")
SET(STREAMING_SRCS "")
add_definitions(-DDISABLE_RTTI)
# include_directories( ${HDRPLUS_ROOT}/${ANDROID_ABI}/include/ZLMediaKit )
# 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(HDRPLUS_LIBS raw exiv2 exiv2-xmp expat lcms2 OpenMP::OpenMP_CXX)
SET(HDRPLUS2_LIBS raw raw_r lcms2 tiff tiffxx jpeg hdrplus_pipeline)
@ -175,9 +185,6 @@ SET(YAMC_INC_DIR ${CMAKE_SOURCE_DIR})
SET(JSONCPP_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp)
SET(JSONCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include)
SET(SQLITE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sqlite)
SET(SQLITE_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sqlite)
SET(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/breakpad)
SET(CAMERA2_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/camera2)
@ -190,17 +197,10 @@ include_directories(${YAMC_INC_DIR})
include_directories(${BREAKPAD_ROOT} ${BREAKPAD_ROOT}/common/android/include)
include_directories(${ASIO_ROOT}/include)
add_library( # Sets the name of the library.
sqlite3
# Sets the library as a shared library.
STATIC
# Provides a relative path to your source file(s).
${SQLITE_SRC_DIR}/sqlite3.c
)
INCLUDE_DIRECTORIES(${SQLITE_INCLUDE_DIR})
# SET(SQLITE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sqlite)
# SET(SQLITE_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sqlite)
# add_library(sqlite3 STATIC ${SQLITE_SRC_DIR}/sqlite3.c )
# INCLUDE_DIRECTORIES(${SQLITE_INCLUDE_DIR})
file(GLOB BREAKPAD_SOURCES_COMMON
native-lib.cpp
@ -340,10 +340,8 @@ include_directories(${TERM_CORE_ROOT})
add_library( # Sets the name of the library.
jsoncpp
# Sets the library as a shared library.
STATIC
# Provides a relative path to your source file(s).
${JSONCPP_SOURCES}
)
@ -399,6 +397,8 @@ add_library( # Sets the name of the library.
netcamera/httpclient.cpp
${STREAMING_SRCS}
#serial/WeatherComm.cpp
# camera2/OpenCVFont.cpp
@ -467,20 +467,15 @@ find_library( # Sets the name of the path variable.
target_link_libraries( # Specifies the target library.
${PROJECT_NAME}
jsoncpp
freetype
# breakpad
# Links the target library to the log library
# included in the NDK.
avcodec avfilter avformat avutil swresample swscale x264
${log-lib}
android camera2ndk mediandk z curl
ncnn ${OpenCV_LIBS} sqlite3 ${HDRPLUS_LIBS_EMBED}
ncnn ${OpenCV_LIBS} sqlite3 ${HDRPLUS_LIBS_EMBED} ${ZLMEDIAKIT_LIBS}
)
# set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS_RELEASE "-strip-all")

@ -1395,10 +1395,12 @@ Java_com_xypower_mpapp_MicroPhotoService_requestPowerControl(
return 0L;
}
extern "C" JNIEXPORT jlong JNICALL
extern "C" JNIEXPORT jboolean JNICALL
Java_com_xypower_mpapp_MicroPhotoService_releasePowerControl(
JNIEnv* env, jclass cls, jlong powerControlHandle) {
PowerControl* powerControl = reinterpret_cast<PowerControl*>(powerControlHandle);
delete powerControl;
return JNI_TRUE;
}

@ -10,6 +10,8 @@
#include "PositionHelper.h"
#include "DngCreator.h"
#include "media/RTSPRecorder.h"
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
@ -1686,6 +1688,142 @@ bool CPhoneDevice::TakePhotoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, c
return true;
}
bool CPhoneDevice::TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& path, std::vector<IDevice::OSD_INFO>& osds, std::shared_ptr<PowerControl> powerCtrlPtr)
{
// AutoEnv autoEnv(pThis->m_vm);
time_t ts = time(NULL);
uint32_t waitTime = localPhotoInfo.selfTestingTime;
if(!GpioControl::GetSelftestStatus(waitTime))
{
m_isSelfTesting.store(true);
waitTime = (waitTime != 0) ? (waitTime * 1024) : 10240;
std::this_thread::sleep_for(std::chrono::milliseconds(waitTime));
m_isSelfTesting.store(false);
}
XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power ON");
// std::shared_ptr<PowerControl> ethernetPowerCtrl = std::make_shared<EthernetPowerCtrl>(1);
std::shared_ptr<PowerControl> ethernetPowerCtrl;
net_handle_t netHandle = GetEthnetHandle();
if (netHandle == 0)
{
// 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;
}
}
}
if (netHandle == 0)
{
// timeout
XYLOG(XYLOG_SEVERITY_ERROR, "Ethernet not existing CH=%u PR=%X PHOTOID=%u", (uint32_t)localPhotoInfo.channel, (uint32_t)localPhotoInfo.preset, localPhotoInfo.photoId);
#ifdef NDEBUG
TakePhotoCb(0, localPhotoInfo, "", 0);
return false;
#endif
}
else
{
XYLOG(XYLOG_SEVERITY_INFO, "Ethernet is Available CH=%u PR=%X PHOTOID=%u", (uint32_t)localPhotoInfo.channel, (uint32_t)localPhotoInfo.preset, localPhotoInfo.photoId);
}
SetStaticIp();
std::this_thread::sleep_for(std::chrono::milliseconds(256));
NET_PHOTO_INFO netPhotoInfo = { netHandle, 0 };
if (localPhotoInfo.vendor == 1)
{
// Hai Kang
netPhotoInfo.authType = HTTP_AUTH_TYPE_DIGEST;
snprintf(netPhotoInfo.url, sizeof(netPhotoInfo.url), "/ISAPI/Streaming/channels/1/picture?");
}
else if (localPhotoInfo.vendor == 2)
{
// Hang Yu
strcpy(netPhotoInfo.url, "/cgi-bin/snapshot.cgi");
}
else if (localPhotoInfo.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)localPhotoInfo.cameraId);
// strcpy(netPhotoInfo.url, "rtsp://192.168.50.224/live/0");
}
else if (localPhotoInfo.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)localPhotoInfo.cameraId);
}
else
{
XYLOG(XYLOG_SEVERITY_ERROR, "Vendor(%u) not Supported CH=%u PR=%X PHOTOID=%u", (uint32_t)localPhotoInfo.vendor, (uint32_t)localPhotoInfo.channel, (unsigned int)localPhotoInfo.preset, localPhotoInfo.photoId);
TakePhotoCb(0, localPhotoInfo, "", 0);
return false;
}
struct in_addr addr;
addr.s_addr = localPhotoInfo.ip;
strcpy(netPhotoInfo.ip, inet_ntoa(addr));
strcpy(netPhotoInfo.outputPath, path.c_str());
if (!localPhotoInfo.userName.empty())
{
size_t len = std::min<size_t>(sizeof(netPhotoInfo.userName) - 1, localPhotoInfo.userName.size());
strncpy(netPhotoInfo.userName, localPhotoInfo.userName.c_str(), len);
}
if (!localPhotoInfo.password.empty())
{
size_t len = std::min<size_t>(sizeof(netPhotoInfo.password) - 1, localPhotoInfo.password.size());
strncpy(netPhotoInfo.password, localPhotoInfo.password.c_str(), len);
}
// strcpy(netPhotoInfo.interface, "eth0");
localPhotoInfo.photoTime = time(NULL);
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(netPhotoInfo.url, tmpFile.c_str(), localPhotoInfo.duration * 1000);
ethernetPowerCtrl.reset();
XYLOG(XYLOG_SEVERITY_DEBUG, "Ethernet Power OFF");
std::string fullPath = endsWith(mPath, ".mp4") ? mPath : (mPath + CTerminal::BuildPhotoFileName(mPhotoInfo));
if (existsFile(tmpFile))
{
std::rename(tmpFile.c_str(), fullPath.c_str());
TakePhotoCb(3, localPhotoInfo, "", localPhotoInfo.photoTime);
}
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,
localPhotoInfo.photoId, netPhotoInfo.ip, netPhotoInfo.url);
}
// Notify to take next photo
// TakePhotoCb(1, localPhotoInfo, "", takingTime);
// 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,
// localPhotoInfo.photoId, netPhotoInfo.ip, netPhotoInfo.url);
// TakePhotoCb(0, localPhotoInfo, "", 0);
return true;
}
bool CPhoneDevice::StartPushStreaming(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& url, std::vector<IDevice::OSD_INFO>& osds, std::shared_ptr<PowerControl> powerCtrlPtr)
{
return true;
@ -1996,6 +2134,71 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector<
t.detach();
}
else if (mPhotoInfo.mediaType == 1 && (mPhotoInfo.cameraType == CAM_TYPE_PLZ))
{
uint64_t wid_serial = RequestWakelock(0);
CPhoneDevice* pThis = this;
IDevice::PHOTO_INFO localPhotoInfo = mPhotoInfo;
IDevice::SerialsPhotoParam param = { "", 0, 0 };
GetPhotoSerialsParamCb(param);
vector<IDevice::OSD_INFO> osds;
osds.swap(mOsds);
std::thread t([localPhotoInfo, param, pThis, path, osds, wid_serial, powerCtrlPtr]() mutable
{
uint32_t waitTime = localPhotoInfo.selfTestingTime;
if(!GpioControl::GetSelftestStatus(waitTime))
{
pThis->m_isSelfTesting.store(true);
time_t remaintime = GpioControl::GetSelfTestRemain(waitTime);
XYLOG(XYLOG_SEVERITY_INFO, "Camera is SeltTesting,remaining selfTestingtime=%u", remaintime);
remaintime = (remaintime != 0) ? (remaintime * 1024) : 10240;
std::this_thread::sleep_for(std::chrono::milliseconds(remaintime));
pThis->m_isSelfTesting.store(false);
XYLOG(XYLOG_SEVERITY_INFO, "Camera SeltTesting is over");
}
if (localPhotoInfo.preset != 0 && localPhotoInfo.preset != 0xFF)
{
XYLOG(XYLOG_SEVERITY_INFO,"Recv CameraCtrl Command, action= MOVE_PRESETNO, preset = %u", localPhotoInfo.preset);
CameraPhotoCmd(time(NULL), localPhotoInfo.channel, MOVE_PRESETNO, 0, localPhotoInfo.preset, param.serfile, param.baud, param.addr);
std::this_thread::sleep_for(std::chrono::seconds(10));
}
pThis->TakeVideoWithNetCamera(localPhotoInfo, path, osds, powerCtrlPtr);
pThis->ReleaseWakelock(wid_serial);
});
t.detach();
}
else if (mPhotoInfo.mediaType == 1 && (mPhotoInfo.cameraType == CAM_TYPE_NET))
{
uint64_t wid_serial = RequestWakelock(0);
CPhoneDevice* pThis = this;
IDevice::PHOTO_INFO localPhotoInfo = mPhotoInfo;
vector<IDevice::OSD_INFO> osds;
osds.swap(mOsds);
std::thread t([localPhotoInfo, pThis, path, osds, wid_serial, powerCtrlPtr]() mutable
{
uint32_t waitTime = localPhotoInfo.selfTestingTime;
if(!GpioControl::GetSelftestStatus(waitTime))
{
pThis->m_isSelfTesting.store(true);
time_t remaintime = GpioControl::GetSelfTestRemain(waitTime);
XYLOG(XYLOG_SEVERITY_INFO, "Camera is SeltTesting,remaining selfTestingtime=%u", remaintime);
remaintime = (remaintime != 0) ? (remaintime * 1024) : 10240;
std::this_thread::sleep_for(std::chrono::milliseconds(remaintime));
pThis->m_isSelfTesting.store(false);
XYLOG(XYLOG_SEVERITY_INFO, "Camera SeltTesting is over");
}
pThis->TakeVideoWithNetCamera(localPhotoInfo, path, osds, powerCtrlPtr);
pThis->ReleaseWakelock(wid_serial);
});
t.detach();
}
else if (mPhotoInfo.usingSysCamera == 1)
{
JNIEnv* env = NULL;

@ -273,6 +273,7 @@ protected:
// bool MatchCaptureSizeRequest(ACameraManager *cameraManager, const char *selectedCameraId, unsigned int width, unsigned int height, uint32_t cameraOrientation_,
bool TakePhotoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& path, std::vector<IDevice::OSD_INFO>& osds, std::shared_ptr<PowerControl> powerCtrlPtr);
bool TakeVideoWithNetCamera(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& path, std::vector<IDevice::OSD_INFO>& osds, std::shared_ptr<PowerControl> powerCtrlPtr);
bool StartPushStreaming(IDevice::PHOTO_INFO& localPhotoInfo, const std::string& url, std::vector<IDevice::OSD_INFO>& osds, std::shared_ptr<PowerControl> powerCtrlPtr);
bool PostProcessPhoto(const PHOTO_INFO& photoInfo, const vector<IDevice::OSD_INFO>& osds, const std::string& path, const std::string& cameraInfo, cv::Mat mat);
inline bool TakePhotoCb(int res, const IDevice::PHOTO_INFO& photoInfo, const string& path, time_t photoTime, const std::vector<IDevice::RECOG_OBJECT>& objects) const

@ -0,0 +1,231 @@
//
// Created by Matthew on 2025/3/1.
//
#include "RTSPRecorder.h"
#include <chrono>
#include <thread>
#include <android/log.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
}
#define LOG_TAG "libcurl"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
void dumpRtmpToMp4(const char* rtmpUrl, const char* outputPath, uint32_t duration)
{
AVFormatContext* inputFormatContext = nullptr;
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);
return;
}
// Retrieve input stream information
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
fprintf(stderr, "Could not find stream information\n");
avformat_close_input(&inputFormatContext);
return;
}
// Open output MP4 file
if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mp4", outputPath) < 0) {
fprintf(stderr, "Could not create output context\n");
avformat_close_input(&inputFormatContext);
return;
}
// 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;
}
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;
}
outStream->codecpar->codec_tag = 0;
}
// Open output file
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "Could not open output file '%s'\n", outputPath);
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return;
}
}
// Write output file header
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
fprintf(stderr, "Error occurred when writing header to output file\n");
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return;
}
// 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);
});
// Read packets from input and write them to output
while (av_read_frame(inputFormatContext, &packet) >= 0) {
AVStream* inStream = inputFormatContext->streams[packet.stream_index];
AVStream* outStream = outputFormatContext->streams[packet.stream_index];
packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);
packet.pos = -1;
if (av_interleaved_write_frame(outputFormatContext, &packet) < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&packet);
}
stop_thread.join();
// Write output file trailer
av_write_trailer(outputFormatContext);
// Clean up
avformat_close_input(&inputFormatContext);
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&outputFormatContext->pb);
}
avformat_free_context(outputFormatContext);
}
void dumpRtspToMp4(const char* rtspUrl, const char* outputPath, uint32_t duration)
{
AVFormatContext* inputFormatContext = nullptr;
AVFormatContext* outputFormatContext = nullptr;
AVPacket packet;
av_register_all();
avformat_network_init();
// Open input RTSP stream
if (avformat_open_input(&inputFormatContext, rtspUrl, nullptr, nullptr) != 0) {
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");
avformat_close_input(&inputFormatContext);
return;
}
// Open output MP4 file
if (avformat_alloc_output_context2(&outputFormatContext, nullptr, "mp4", outputPath) < 0) {
fprintf(stderr, "Could not create output context\n");
avformat_close_input(&inputFormatContext);
return;
}
// 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;
}
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;
}
outStream->codecpar->codec_tag = 0;
}
// Open output file
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "Could not open output file '%s'\n", outputPath);
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return;
}
}
// Write output file header
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
fprintf(stderr, "Error occurred when writing header to output file\n");
avformat_close_input(&inputFormatContext);
avformat_free_context(outputFormatContext);
return;
}
// 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);
});
// Read packets from input and write them to output
while (av_read_frame(inputFormatContext, &packet) >= 0) {
AVStream* inStream = inputFormatContext->streams[packet.stream_index];
AVStream* outStream = outputFormatContext->streams[packet.stream_index];
packet.pts = av_rescale_q_rnd(packet.pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.dts = av_rescale_q_rnd(packet.dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, inStream->time_base, outStream->time_base);
packet.pos = -1;
if (av_interleaved_write_frame(outputFormatContext, &packet) < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&packet);
}
stop_thread.join();
// Write output file trailer
av_write_trailer(outputFormatContext);
// Clean up
avformat_close_input(&inputFormatContext);
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&outputFormatContext->pb);
}
avformat_free_context(outputFormatContext);
}

@ -0,0 +1,19 @@
//
// Created by Matthew on 2025/3/1.
//
#ifndef MICROPHOTO_RTSPRECORDER_H
#define MICROPHOTO_RTSPRECORDER_H
#include <string>
// 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);
class RTSPRecorder {
};
#endif //MICROPHOTO_RTSPRECORDER_H

@ -0,0 +1,186 @@
//
// Created by Matthew on 2025/2/28.
//
#include "RTSPToMP4.h"
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <limits>
int32_t getMaxInputSize(AMediaExtractor* extractor, size_t trackIndex)
{
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackIndex);
int32_t maxInputSize = 0;
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, &maxInputSize)) {
// LOGI("Max input size for track %zu: %d", trackIndex, maxInputSize);
} else {
// LOGE("Failed to get max input size for track %zu", trackIndex);
}
AMediaFormat_delete(format);
return maxInputSize;
}
RTSPToMP4::RTSPToMP4(const char* rtspUrl, const char* outputPath, uint64_t durationInMs/* = 0*/)
: fd(-1), codec(nullptr), extractor(nullptr), muxer(nullptr), videoTrackIndex(-1), durationInMs(durationInMs), running(false) {
initExtractor(rtspUrl);
initCodec("video/avc");
initMuxer(outputPath);
}
RTSPToMP4::~RTSPToMP4() {
if (codec) AMediaCodec_delete(codec);
if (extractor) AMediaExtractor_delete(extractor);
if (muxer) AMediaMuxer_delete(muxer);
if (fd != -1)
{
fdatasync(fd);
close(fd);
fd = -1;
}
}
void RTSPToMP4::initCodec(const char* mime) {
codec = AMediaCodec_createDecoderByType(mime);
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime);
// Set other format parameters as needed
// ...
AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
AMediaFormat_delete(format);
}
void RTSPToMP4::initExtractor(const char* rtspUrl) {
extractor = AMediaExtractor_new();
media_status_t status = AMediaExtractor_setDataSource(extractor, rtspUrl);
if (status != AMEDIA_OK) {
// Handle error
// ...
}
}
void RTSPToMP4::initMuxer(const char* outputPath) {
fd = open(outputPath, O_CREAT | O_WRONLY, 0644);
muxer = AMediaMuxer_new(fd, AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
int numTracks = AMediaExtractor_getTrackCount(extractor);
if (numTracks <= 0) {
// LOGE("No tracks found in RTSP stream");
AMediaExtractor_delete(extractor);
return;
}
for (int i = 0; i < numTracks; ++i) {
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, i);
const char* mime;
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime) && strncmp(mime, "video/", 6) == 0) {
videoTrackIndex = AMediaMuxer_addTrack(muxer, format);
AMediaExtractor_selectTrack(extractor, i);
}
AMediaFormat_delete(format);
}
if (videoTrackIndex == -1) {
// LOGE("No video track found in RTSP stream");
AMediaExtractor_delete(extractor);
AMediaMuxer_delete(muxer);
return;
}
int32_t maxInputSize = getMaxInputSize(extractor, videoTrackIndex);
if (maxInputSize <= 0) {
// LOGE("Invalid max input size");
// releaseMediaExtractor(extractor);
sampleData.resize(1920 * 1080 * 4, 0);
return;
}
sampleData.resize(maxInputSize, 0);
}
void RTSPToMP4::startDecodingAndMuxing() {
AMediaCodec_start(codec);
size_t bufferSize = sampleData.size();
uint8_t* buffer = &sampleData[0];
int64_t sampleTime = 0;
int64_t startTime = 0;
bool firstSampleData = true;
int64_t durationTime = (durationInMs == 0) ? std::numeric_limits<int64_t>::max() : (int64_t)durationInMs * 1000;
while (running) {
// Extract data from RTSP stream
ssize_t sampleSize = AMediaExtractor_readSampleData(extractor, buffer, bufferSize);
if (sampleSize < 0) {
break; // End of stream
}
sampleTime = AMediaExtractor_getSampleTime(extractor);
if (firstSampleData)
{
startTime = sampleTime;
firstSampleData = false;
}
sampleTime -= startTime;
// Feed data to codec
size_t inputBufferIndex;
uint8_t* inputBuffer = AMediaCodec_getInputBuffer(codec, inputBufferIndex, &bufferSize);
memcpy(inputBuffer, buffer, sampleSize);
AMediaCodec_queueInputBuffer(codec, inputBufferIndex, 0, sampleSize, sampleTime, 0);
// Retrieve decoded frames and write to muxer
AMediaCodecBufferInfo bufferInfo;
ssize_t outputBufferIndex = AMediaCodec_dequeueOutputBuffer(codec, &bufferInfo, 0);
if (outputBufferIndex >= 0) {
bufferInfo.offset = 0;
bufferInfo.size = sampleSize;
bufferInfo.presentationTimeUs = sampleTime;
bufferInfo.flags = AMediaExtractor_getSampleFlags(extractor);
uint8_t* outputBuffer = AMediaCodec_getOutputBuffer(codec, outputBufferIndex, &bufferSize);
AMediaMuxer_writeSampleData(muxer, videoTrackIndex, outputBuffer, &bufferInfo);
AMediaCodec_releaseOutputBuffer(codec, outputBufferIndex, false);
}
AMediaExtractor_advance(extractor);
if (sampleTime > durationTime)
{
break;
}
}
AMediaCodec_stop(codec);
AMediaMuxer_stop(muxer);
if (fd != -1)
{
fdatasync(fd);
close(fd);
fd = -1;
}
}
void RTSPToMP4::start() {
// Add video track to muxer
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, 0);
videoTrackIndex = AMediaMuxer_addTrack(muxer, format);
running = true;
AMediaMuxer_start(muxer);
startDecodingAndMuxing();
}
void RTSPToMP4::stop() {
running = false;
}

@ -0,0 +1,38 @@
//
// Created by Matthew on 2025/2/28.
//
#ifndef MICROPHOTO_RTSPTOMP4_H
#define MICROPHOTO_RTSPTOMP4_H
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaExtractor.h>
#include <media/NdkMediaMuxer.h>
#include <vector>
class RTSPToMP4 {
public:
RTSPToMP4(const char* rtspUrl, const char* outputPath, uint64_t durationInMs = 0);
~RTSPToMP4();
void start();
void stop();
private:
void initCodec(const char* mime);
void initExtractor(const char* rtspUrl);
void initMuxer(const char* outputPath);
void startDecodingAndMuxing();
int fd;
AMediaCodec* codec;
AMediaExtractor* extractor;
AMediaMuxer* muxer;
int videoTrackIndex;
uint64_t durationInMs;
bool running;
std::vector<uint8_t> sampleData;
};
#endif //MICROPHOTO_RTSPTOMP4_H

@ -46,6 +46,9 @@ public class BridgeProvider extends ContentProvider {
private final static String PATH_RECOG_PIC = "/recogPic";
private final static String PATH_REQUEST_PWR_CTRL = "/requestPwrCtrl";
private final static String PATH_RELEASE_PWR_CTRL = "/releasePwrCtrl";
public BridgeProvider() {
Log.i(TAG, "BridgeProvider");
}
@ -85,6 +88,9 @@ public class BridgeProvider extends ContentProvider {
matcher.addURI(AUTHORITY, PATH_QUERY_SEC_VERSION, 1);
matcher.addURI(AUTHORITY, PATH_QUERY_BATTERY_VOLTAGE, 2);
matcher.addURI(AUTHORITY, PATH_RECOG_PIC, 3);
matcher.addURI(AUTHORITY, PATH_REQUEST_PWR_CTRL, 4);
matcher.addURI(AUTHORITY, PATH_RELEASE_PWR_CTRL, 5);
Cursor cursor = null;
int matched = matcher.match(uri);
@ -98,6 +104,12 @@ public class BridgeProvider extends ContentProvider {
case 3:
cursor = recoganizePicture(uri, selection, selectionArgs);
break;
case 4:
cursor = requestPowerControl(uri, selection, selectionArgs);
break;
case 5:
cursor = recoganizePicture(uri, selection, selectionArgs);
break;
default:
break;
}
@ -169,6 +181,48 @@ public class BridgeProvider extends ContentProvider {
return matrixCursor;
}
private Cursor requestPowerControl(Uri uri, String selection, String[] selectionArgs) {
String decodedSelection = stringFromBase64(selection);
int type = 0;
if (!TextUtils.isEmpty(decodedSelection)) {
Uri u = Uri.parse("http://a.com/?" + decodedSelection);
String val = u.getQueryParameter("type");
try {
type = Integer.parseInt(val);
} catch (Exception ex) {
ex.printStackTrace();
}
}
long nativeHandle = MicroPhotoService.requestPowerControl(type);
String[] columns = { "pwrCtrl" };
MatrixCursor matrixCursor = new MatrixCursor(columns, 1);
matrixCursor.addRow(new Object[] { Long.valueOf(nativeHandle) });
return matrixCursor;
}
private Cursor releasePowerControl(Uri uri, String selection, String[] selectionArgs) {
String decodedSelection = stringFromBase64(selection);
long nativeHandle = 0;
if (!TextUtils.isEmpty(decodedSelection)) {
Uri u = Uri.parse("http://a.com/?" + decodedSelection);
String val = u.getQueryParameter("handle");
try {
nativeHandle = Long.parseLong(val);
} catch (Exception ex) {
ex.printStackTrace();
}
}
boolean res = MicroPhotoService.releasePowerControl(nativeHandle);
String[] columns = { "result" };
MatrixCursor matrixCursor = new MatrixCursor(columns, 1);
matrixCursor.addRow(new Object[] { Integer.valueOf(res ? 1 : 0) });
return matrixCursor;
}
private Cursor recoganizePicture(Uri uri, String selection, String[] selectionArgs) {
String decodedSelection = stringFromBase64(selection);

@ -91,6 +91,7 @@ public class MicroPhotoService extends Service {
// Used to load the 'microphoto' library on application startup.
static {
loadLibrary("microphoto");
}
@ -1681,7 +1682,7 @@ cellSignalStrengthGsm.getDbm();
public static native boolean exportPrivateFile(int index, String outputPath);
public static native long requestPowerControl(int type);
public static native long releasePowerControl(long powerControlHandle);
public static native boolean releasePowerControl(long powerControlHandle);
////////////////////////GPS////////////////////
// private static final String GPS_LOCATION_NAME = android.location.LocationManager.GPS_PROVIDER;

Loading…
Cancel
Save