|
|
@ -4,12 +4,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
#include "Streaming.h"
|
|
|
|
#include "Streaming.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
|
|
|
|
#include <android/api-level.h>
|
|
|
|
#include <android/api-level.h>
|
|
|
|
#include <android/log.h>
|
|
|
|
#include <android/log.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
|
|
|
|
#include <libavutil/avutil.h>
|
|
|
|
|
|
|
|
#include <libavutil/opt.h>
|
|
|
|
|
|
|
|
#include <libavutil/time.h>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl);
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#if 0
|
|
|
|
StreamForwarder::~StreamForwarder() {
|
|
|
|
StreamForwarder::~StreamForwarder() {
|
|
|
|
stop();
|
|
|
|
stop();
|
|
|
@ -159,94 +174,213 @@ void StreamForwarder::processFrame(AVFrame* frame) {
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool StreamForwarder::initialize(const std::string& input, const std::string& output) {
|
|
|
|
RtspForwarder::RtspForwarder(const std::string& input, const std::string& output)
|
|
|
|
inputUrl = input;
|
|
|
|
: inputUrl(input), outputUrl(output), isRunning(false)
|
|
|
|
outputUrl = output;
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool RtspForwarder::isStreaming() const
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return isRunning;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (avformat_open_input(&inputFormatContext, inputUrl.c_str(), nullptr, nullptr) < 0) {
|
|
|
|
bool RtspForwarder::start()
|
|
|
|
return false;
|
|
|
|
{
|
|
|
|
|
|
|
|
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) {
|
|
|
|
// Input options
|
|
|
|
cleanup();
|
|
|
|
AVDictionary* inputOptions = nullptr;
|
|
|
|
return false;
|
|
|
|
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",
|
|
|
|
// Get stream info
|
|
|
|
outputUrl.c_str()) < 0) {
|
|
|
|
ret = avformat_find_stream_info(inputFormatContext, nullptr);
|
|
|
|
cleanup();
|
|
|
|
if (ret < 0) {
|
|
|
|
return false;
|
|
|
|
// 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 (videoStreamIndex == -1) {
|
|
|
|
{
|
|
|
|
// std::cerr << "No video stream found" << std::endl;
|
|
|
|
if (!inputFormatContext || !outputFormatContext) {
|
|
|
|
avformat_close_input(&inputFormatContext);
|
|
|
|
return false;
|
|
|
|
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* inStream = inputFormatContext->streams[i];
|
|
|
|
AVStream* outStream = avformat_new_stream(outputFormatContext,
|
|
|
|
AVCodecParameters* inCodecpar = inStream->codecpar;
|
|
|
|
inStream->codec->codec);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AVStream* outStream = avformat_new_stream(outputFormatContext, nullptr);
|
|
|
|
if (!outStream) {
|
|
|
|
if (!outStream) {
|
|
|
|
cleanup();
|
|
|
|
std::cerr << "Failed to allocate output stream" << std::endl;
|
|
|
|
return false;
|
|
|
|
avformat_close_input(&inputFormatContext);
|
|
|
|
|
|
|
|
avformat_free_context(outputFormatContext);
|
|
|
|
|
|
|
|
return AVERROR_UNKNOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (avcodec_parameters_copy(outStream->codecpar,
|
|
|
|
ret = avcodec_parameters_copy(outStream->codecpar, inCodecpar);
|
|
|
|
inStream->codecpar) < 0) {
|
|
|
|
if (ret < 0) {
|
|
|
|
cleanup();
|
|
|
|
std::cerr << "Failed to copy codec parameters" << std::endl;
|
|
|
|
return false;
|
|
|
|
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 (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
|
|
|
if (avio_open(&outputFormatContext->pb, outputUrl.c_str(),
|
|
|
|
ret = avio_open(&outputFormatContext->pb, outputUrl.c_str(), AVIO_FLAG_WRITE);
|
|
|
|
AVIO_FLAG_WRITE) < 0) {
|
|
|
|
if (ret < 0) {
|
|
|
|
cleanup();
|
|
|
|
std::cerr << "Could not open output URL: " << av_err2str(ret) << std::endl;
|
|
|
|
return false;
|
|
|
|
avformat_close_input(&inputFormatContext);
|
|
|
|
|
|
|
|
avformat_free_context(outputFormatContext);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
|
|
|
|
// Write header
|
|
|
|
cleanup();
|
|
|
|
ret = avformat_write_header(outputFormatContext, &outputOptions);
|
|
|
|
return false;
|
|
|
|
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;
|
|
|
|
AVPacket packet;
|
|
|
|
while (!stopRequested && av_read_frame(inputFormatContext, &packet) >= 0) {
|
|
|
|
while (isRunning) {
|
|
|
|
av_write_frame(outputFormatContext, &packet);
|
|
|
|
ret = av_read_frame(inputFormatContext, &packet);
|
|
|
|
av_packet_unref(&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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
av_write_trailer(outputFormatContext);
|
|
|
|
// Fix timestamps if enabled
|
|
|
|
return true;
|
|
|
|
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::stop()
|
|
|
|
if (packet.pts == AV_NOPTS_VALUE) {
|
|
|
|
{
|
|
|
|
// Generate PTS if missing
|
|
|
|
stopRequested = true;
|
|
|
|
if (startTime == AV_NOPTS_VALUE) {
|
|
|
|
return true;
|
|
|
|
startTime = av_gettime();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
packet.pts = av_rescale_q(av_gettime() - startTime,
|
|
|
|
|
|
|
|
AV_TIME_BASE_Q,
|
|
|
|
|
|
|
|
inStream->time_base);
|
|
|
|
|
|
|
|
packet.dts = packet.pts;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool StreamForwarder::isStreaming() const
|
|
|
|
// Rescale timestamps to output timebase
|
|
|
|
{
|
|
|
|
packet.pts = av_rescale_q_rnd(packet.pts,
|
|
|
|
return !stopRequested;
|
|
|
|
inStream->time_base,
|
|
|
|
}
|
|
|
|
outStream->time_base,
|
|
|
|
|
|
|
|
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
|
|
|
|
|
|
|
packet.dts = av_rescale_q_rnd(packet.dts,
|
|
|
|
|
|
|
|
inStream->time_base,
|
|
|
|
|
|
|
|
outStream->time_base,
|
|
|
|
|
|
|
|
static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
|
|
|
|
|
|
|
|
packet.duration = av_rescale_q(packet.duration,
|
|
|
|
|
|
|
|
inStream->time_base,
|
|
|
|
|
|
|
|
outStream->time_base);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void StreamForwarder::cleanup() {
|
|
|
|
// Write packet to output
|
|
|
|
if (inputFormatContext) {
|
|
|
|
ret = av_interleaved_write_frame(outputFormatContext, &packet);
|
|
|
|
avformat_close_input(&inputFormatContext);
|
|
|
|
av_packet_unref(&packet);
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
|
|
|
std::cerr << "Error writing frame: " << av_err2str(ret) << std::endl;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (outputFormatContext) {
|
|
|
|
// Write trailer
|
|
|
|
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
|
|
|
|
av_write_trailer(outputFormatContext);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
|
|
|
avformat_close_input(&inputFormatContext);
|
|
|
|
|
|
|
|
if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE))
|
|
|
|
avio_closep(&outputFormatContext->pb);
|
|
|
|
avio_closep(&outputFormatContext->pb);
|
|
|
|
}
|
|
|
|
|
|
|
|
avformat_free_context(outputFormatContext);
|
|
|
|
avformat_free_context(outputFormatContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|