|
|
@@ -2,26 +2,183 @@
|
|
|
#include "av_muxer.h"
|
|
|
|
|
|
#include <QDebug>
|
|
|
+#include <chrono>
|
|
|
+#include <errno.h>
|
|
|
|
|
|
bool AvMuxer::Open(std::string_view filePath, std::string_view format)
|
|
|
{
|
|
|
Close();
|
|
|
_isOpenFile = false;
|
|
|
_filePath = filePath;
|
|
|
- __CheckBool(avformat_alloc_output_context2(&_fmtCtx, nullptr, format.data(), _filePath.c_str()) >= 0);
|
|
|
- __CheckBool(_fmtCtx);
|
|
|
+
|
|
|
+ qDebug() << "AvMuxer::Open: Opening file" << QString::fromStdString(_filePath) << "with format" << QString::fromStdString(std::string(format));
|
|
|
+
|
|
|
+ int ret = avformat_alloc_output_context2(&_fmtCtx, nullptr, format.data(), _filePath.c_str());
|
|
|
+ if (ret < 0) {
|
|
|
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
+ av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
|
+ qDebug() << "AvMuxer::Open failed: Cannot allocate output context. Error:" << errbuf << "(" << ret << ")";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!_fmtCtx) {
|
|
|
+ qDebug() << "AvMuxer::Open failed: Format context is null after allocation";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ qDebug() << "AvMuxer::Open: Format context created successfully";
|
|
|
+ qDebug() << "Output format:" << _fmtCtx->oformat->name << "long name:" << _fmtCtx->oformat->long_name;
|
|
|
+ qDebug() << "Video codec:" << avcodec_get_name(_fmtCtx->oformat->video_codec);
|
|
|
+ qDebug() << "Audio codec:" << avcodec_get_name(_fmtCtx->oformat->audio_codec);
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool AvMuxer::WriteHeader()
|
|
|
{
|
|
|
- av_dump_format(_fmtCtx, 0, _filePath.data(), 1);
|
|
|
- // 打开输出文件
|
|
|
- if (!(_fmtCtx->oformat->flags & AVFMT_NOFILE)) {
|
|
|
- __CheckBool(avio_open(&_fmtCtx->pb, _filePath.c_str(), AVIO_FLAG_WRITE) >= 0);
|
|
|
+ // av_dump_format(_fmtCtx, 0, _filePath.data(), 1);
|
|
|
+
|
|
|
+ // 检查格式上下文是否有效
|
|
|
+ if (!_fmtCtx) {
|
|
|
+ qDebug() << "WriteHeader failed: Format context is null";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查输出格式是否有效
|
|
|
+ if (!_fmtCtx->oformat) {
|
|
|
+ qDebug() << "WriteHeader failed: Output format is null";
|
|
|
+ return false;
|
|
|
}
|
|
|
+
|
|
|
+ qDebug() << "WriteHeader: Opening file" << QString::fromStdString(_filePath) << "with format" << _fmtCtx->oformat->name;
|
|
|
+
|
|
|
+ // 检查是否为RTMP流
|
|
|
+ bool isRtmpStream = _filePath.find("rtmp://") == 0;
|
|
|
+ if (isRtmpStream) {
|
|
|
+ qDebug() << "WriteHeader: Detected RTMP stream, setting network options";
|
|
|
+
|
|
|
+ // 解析RTMP URL获取服务器信息
|
|
|
+ std::string rtmpUrl = _filePath;
|
|
|
+ std::string serverInfo = "Unknown";
|
|
|
+ size_t protocolEnd = rtmpUrl.find("://");
|
|
|
+ if (protocolEnd != std::string::npos) {
|
|
|
+ size_t serverStart = protocolEnd + 3;
|
|
|
+ size_t serverEnd = rtmpUrl.find("/", serverStart);
|
|
|
+ if (serverEnd != std::string::npos) {
|
|
|
+ serverInfo = rtmpUrl.substr(serverStart, serverEnd - serverStart);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ qDebug() << "WriteHeader: RTMP server:" << QString::fromStdString(serverInfo);
|
|
|
+
|
|
|
+ // 设置RTMP连接选项
|
|
|
+ AVDictionary* options = nullptr;
|
|
|
+ av_dict_set(&options, "rtmp_live", "1", 0); // 设置为直播模式
|
|
|
+ av_dict_set(&options, "rtmp_buffer", "1000", 0); // 设置缓冲区大小
|
|
|
+ av_dict_set(&options, "timeout", "3000000", 0); // 设置连接超时(3秒,单位微秒)
|
|
|
+ av_dict_set(&options, "rw_timeout", "3000000", 0); // 设置读写超时(3秒)
|
|
|
+ av_dict_set(&options, "stimeout", "3000000", 0); // 设置socket超时(3秒)
|
|
|
+ av_dict_set(&options, "rtmp_conn", "S:publish", 0); // 设置发布模式
|
|
|
+ av_dict_set(&options, "rtmp_tcurl", _filePath.c_str(), 0); // 设置RTMP URL
|
|
|
+ av_dict_set(&options, "rtmp_flashver", "FMLE/3.0 (compatible; FMSc/1.0)", 0); // 设置Flash版本
|
|
|
+
|
|
|
+ qDebug() << "WriteHeader: RTMP options set - live mode, 1000ms buffer, 3s timeout";
|
|
|
+ qDebug() << "WriteHeader: Multiple timeout mechanisms enabled (timeout, rw_timeout, stimeout)";
|
|
|
+
|
|
|
+ // 打开RTMP流
|
|
|
+ if (!(_fmtCtx->oformat->flags & AVFMT_NOFILE)) {
|
|
|
+ qDebug() << "WriteHeader: Attempting RTMP connection with enhanced timeout protection...";
|
|
|
+
|
|
|
+ // 记录开始时间用于额外的超时检查
|
|
|
+ auto startTime = std::chrono::steady_clock::now();
|
|
|
+
|
|
|
+ int ret = avio_open(&_fmtCtx->pb, _filePath.c_str(), AVIO_FLAG_WRITE);
|
|
|
+
|
|
|
+ // 检查连接时间
|
|
|
+ auto endTime = std::chrono::steady_clock::now();
|
|
|
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
|
|
|
+ qDebug() << "WriteHeader: RTMP connection attempt took" << duration.count() << "ms";
|
|
|
+ if (ret < 0) {
|
|
|
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
+ av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
|
+ qDebug() << "WriteHeader failed: Cannot connect to RTMP server" << QString::fromStdString(_filePath);
|
|
|
+ qDebug() << "Error:" << errbuf << "(" << ret << ")";
|
|
|
+ qDebug() << "RTMP server:" << QString::fromStdString(serverInfo);
|
|
|
+ qDebug() << "Connection duration:" << duration.count() << "ms";
|
|
|
+
|
|
|
+ // 检查是否为超时相关问题
|
|
|
+ if (duration.count() >= 2900) { // 接近3秒超时
|
|
|
+ qDebug() << "WARNING: Connection attempt took nearly full timeout period!";
|
|
|
+ qDebug() << "This suggests network connectivity issues or server overload.";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 针对特定错误码提供详细解释
|
|
|
+ if (ret == -10049) {
|
|
|
+ qDebug() << "Error Analysis: WSAEADDRNOTAVAIL - Address not available";
|
|
|
+ qDebug() << "This error typically means:";
|
|
|
+ qDebug() << "- The RTMP server is not running or not listening on port 1935";
|
|
|
+ qDebug() << "- The IP address 192.168.3.76 is not reachable from this machine";
|
|
|
+ qDebug() << "- Network routing issues or firewall blocking";
|
|
|
+ } else if (ret == -111) {
|
|
|
+ qDebug() << "Error Analysis: Connection refused";
|
|
|
+ qDebug() << "The server actively refused the connection";
|
|
|
+ } else if (ret == -110 || ret == -ETIMEDOUT) {
|
|
|
+ qDebug() << "Error Analysis: Connection timeout";
|
|
|
+ qDebug() << "The server did not respond within the timeout period";
|
|
|
+ qDebug() << "Consider increasing timeout or checking network latency";
|
|
|
+ } else if (ret == -ECONNRESET) {
|
|
|
+ qDebug() << "Error Analysis: Connection reset by peer";
|
|
|
+ qDebug() << "The server closed the connection unexpectedly";
|
|
|
+ }
|
|
|
+
|
|
|
+ qDebug() << "Enhanced Troubleshooting steps:";
|
|
|
+ qDebug() << "1. Quick network test: ping -n 1 -w 1000" << QString::fromStdString(serverInfo.substr(0, serverInfo.find(':')));
|
|
|
+ qDebug() << "2. Port connectivity: telnet" << QString::fromStdString(serverInfo) << "(should connect immediately)";
|
|
|
+ qDebug() << "3. Verify RTMP server status and logs on" << QString::fromStdString(serverInfo);
|
|
|
+ qDebug() << "4. Check if server accepts RTMP publish connections";
|
|
|
+ qDebug() << "5. Test with reduced timeout: try 1-2 second timeout first";
|
|
|
+ qDebug() << "6. Verify stream key and application name in URL";
|
|
|
+ qDebug() << "7. Check server-side connection limits and authentication";
|
|
|
+ qDebug() << "8. Test with FFmpeg: ffmpeg -re -f lavfi -i testsrc=duration=10:size=320x240:rate=30 -c:v libx264 -f flv" << QString::fromStdString(_filePath);
|
|
|
+ av_dict_free(&options);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ av_dict_free(&options);
|
|
|
+ qDebug() << "WriteHeader: RTMP connection established successfully to" << QString::fromStdString(serverInfo);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 打开本地文件
|
|
|
+ if (!(_fmtCtx->oformat->flags & AVFMT_NOFILE)) {
|
|
|
+ int ret = avio_open(&_fmtCtx->pb, _filePath.c_str(), AVIO_FLAG_WRITE);
|
|
|
+ if (ret < 0) {
|
|
|
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
+ av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
|
+ qDebug() << "WriteHeader failed: Cannot open output file" << QString::fromStdString(_filePath)
|
|
|
+ << "Error:" << errbuf << "(" << ret << ")";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ qDebug() << "WriteHeader: Local file opened successfully";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 写入文件头
|
|
|
- __CheckBool(avformat_write_header(_fmtCtx, nullptr) >= 0);
|
|
|
+ int ret = avformat_write_header(_fmtCtx, nullptr);
|
|
|
+ if (ret < 0) {
|
|
|
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
+ av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
|
+ qDebug() << "WriteHeader failed: Cannot write header. Error:" << errbuf << "(" << ret << ")";
|
|
|
+
|
|
|
+ // 输出流信息用于调试
|
|
|
+ qDebug() << "Format context info:";
|
|
|
+ qDebug() << " - Number of streams:" << _fmtCtx->nb_streams;
|
|
|
+ for (unsigned int i = 0; i < _fmtCtx->nb_streams; i++) {
|
|
|
+ AVStream* stream = _fmtCtx->streams[i];
|
|
|
+ qDebug() << " - Stream" << i << "codec:" << avcodec_get_name(stream->codecpar->codec_id)
|
|
|
+ << "type:" << av_get_media_type_string(stream->codecpar->codec_type);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ qDebug() << "WriteHeader: Header written successfully";
|
|
|
// _fmtCtx->flags |= AVFMT_FLAG_NOBUFFER; // 无缓冲
|
|
|
// _fmtCtx->flags |= AVFMT_FLAG_FLUSH_PACKETS; // 立即刷新数据包
|
|
|
_isOpenFile = true;
|
|
|
@@ -30,31 +187,71 @@ bool AvMuxer::WriteHeader()
|
|
|
|
|
|
int AvMuxer::AddVideoStream(const Encoder<MediaType::VIDEO>::Param& param)
|
|
|
{
|
|
|
- __Check(-1, _fmtCtx->oformat->video_codec != AV_CODEC_ID_NONE);
|
|
|
+ if (_fmtCtx->oformat->video_codec == AV_CODEC_ID_NONE) {
|
|
|
+ qDebug() << "AddVideoStream failed: Output format does not support video";
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ qDebug() << "AddVideoStream: Creating video encoder with codec" << avcodec_get_name(_fmtCtx->oformat->video_codec)
|
|
|
+ << "resolution" << param.width << "x" << param.height << "fps" << param.fps;
|
|
|
+
|
|
|
Info info;
|
|
|
info.pts = 0;
|
|
|
info.fps = param.fps;
|
|
|
auto encoder = new Encoder<MediaType::VIDEO>;
|
|
|
- __Check(-1, encoder->Open(param, _fmtCtx));
|
|
|
+
|
|
|
+ if (!encoder->Open(param, _fmtCtx)) {
|
|
|
+ qDebug() << "AddVideoStream failed: Cannot open video encoder";
|
|
|
+ delete encoder;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
info.type = MediaType::VIDEO;
|
|
|
info.encoder = encoder;
|
|
|
- __Check(-1, _AddStream(info));
|
|
|
+
|
|
|
+ if (!_AddStream(info)) {
|
|
|
+ qDebug() << "AddVideoStream failed: Cannot add video stream to format context";
|
|
|
+ delete encoder;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
_infos.back().stream->time_base = {1, info.fps};
|
|
|
+ qDebug() << "AddVideoStream: Video stream added successfully, index:" << info.streamIndex;
|
|
|
return info.streamIndex;
|
|
|
}
|
|
|
|
|
|
int AvMuxer::AddAudioStream(const Encoder<MediaType::AUDIO>::Param& param)
|
|
|
{
|
|
|
- __Check(-1, _fmtCtx->oformat->audio_codec != AV_CODEC_ID_NONE);
|
|
|
+ if (_fmtCtx->oformat->audio_codec == AV_CODEC_ID_NONE) {
|
|
|
+ qDebug() << "AddAudioStream failed: Output format does not support audio";
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ qDebug() << "AddAudioStream: Creating audio encoder with codec"
|
|
|
+ << avcodec_get_name(_fmtCtx->oformat->audio_codec);
|
|
|
+ // << "sample rate" << param.sampleRate << "channels" << param.channels;
|
|
|
+
|
|
|
Info info;
|
|
|
info.pts = 0;
|
|
|
info.fps = AUDIO_SAMPLE_RATE;
|
|
|
auto encoder = new Encoder<MediaType::AUDIO>;
|
|
|
info.type = MediaType::AUDIO;
|
|
|
info.encoder = encoder;
|
|
|
- __Check(-1, encoder->Open(param, _fmtCtx));
|
|
|
- __Check(-1, _AddStream(info));
|
|
|
+
|
|
|
+ if (!encoder->Open(param, _fmtCtx)) {
|
|
|
+ qDebug() << "AddAudioStream failed: Cannot open audio encoder";
|
|
|
+ delete encoder;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!_AddStream(info)) {
|
|
|
+ qDebug() << "AddAudioStream failed: Cannot add audio stream to format context";
|
|
|
+ delete encoder;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
_infos.back().stream->time_base = {1, AUDIO_SAMPLE_RATE};
|
|
|
+ qDebug() << "AddAudioStream: Audio stream added successfully, index:" << info.streamIndex;
|
|
|
return info.streamIndex;
|
|
|
}
|
|
|
|
|
|
@@ -151,13 +348,28 @@ void AvMuxer::Close()
|
|
|
|
|
|
bool AvMuxer::_AddStream(Info& info)
|
|
|
{
|
|
|
- __CheckBool(info.stream = avformat_new_stream(_fmtCtx, nullptr));
|
|
|
+ info.stream = avformat_new_stream(_fmtCtx, nullptr);
|
|
|
+ if (!info.stream) {
|
|
|
+ qDebug() << "_AddStream failed: Cannot create new stream";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
info.stream->id = _fmtCtx->nb_streams - 1;
|
|
|
- __CheckBool(avcodec_parameters_from_context(info.stream->codecpar, info.encoder->GetCtx()) >= 0);
|
|
|
+
|
|
|
+ int ret = avcodec_parameters_from_context(info.stream->codecpar, info.encoder->GetCtx());
|
|
|
+ if (ret < 0) {
|
|
|
+ char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
|
+ av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
|
|
+ qDebug() << "_AddStream failed: Cannot copy codec parameters. Error:" << errbuf << "(" << ret << ")";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
info.streamIndex = _fmtCtx->nb_streams - 1;
|
|
|
info.pts = 0;
|
|
|
info.isEnd = false;
|
|
|
_infos.push_back(info);
|
|
|
+
|
|
|
+ qDebug() << "_AddStream: Stream added successfully, index:" << info.streamIndex;
|
|
|
return true;
|
|
|
}
|
|
|
|