#include "av_muxer.h" #include #include #include bool AvMuxer::Open(std::string_view filePath, std::string_view format) { Close(); _isOpenFile = false; _filePath = filePath; 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) { 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(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"; } } // 写入文件头 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; return true; } int AvMuxer::AddVideoStream(const Encoder::Param& param) { 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; if (!encoder->Open(param, _fmtCtx)) { qDebug() << "AddVideoStream failed: Cannot open video encoder"; delete encoder; return -1; } info.type = MediaType::VIDEO; info.encoder = encoder; 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::Param& param) { 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; info.type = MediaType::AUDIO; info.encoder = encoder; 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; } bool AvMuxer::Write(AVFrame* frame, int streamIndex, bool isEnd) { std::lock_guard lk(_mtx); __CheckBool(_infos.size() > streamIndex); auto&& info = _infos[streamIndex]; if (info.isEnd) { return true; } if (isEnd) { info.isEnd = isEnd; frame = nullptr; } __CheckBool(info.encoder); // 只在有多个活跃流且音频流有数据时才做同步检查 int activeStreamCount = 0; int audioStreamIdx = -1; for (int i = 0; i < _infos.size(); ++i) { if (!_infos[i].isEnd && _infos[i].pts > 0) { ++activeStreamCount; if (_infos[i].type == MediaType::AUDIO) audioStreamIdx = i; } } // 只在视频流超前音频流时丢帧,音频流超前时不阻塞视频帧写入 if (activeStreamCount > 1 && audioStreamIdx != -1) { double curTime = double(info.pts) / info.fps; double audioTime = double(_infos[audioStreamIdx].pts) / _infos[audioStreamIdx].fps; if (info.type == MediaType::VIDEO && curTime - audioTime > 0.7) { // 视频流超前音频流太多才丢帧 return false; } } info.isEncodeOverload = false; if (!info.encoder->PushFrame(frame, isEnd, info.pts)) { return false; } // 调试输出音视频流的pts // if (info.type == MediaType::AUDIO) { // qDebug() << "AUDIO PTS:" << info.pts; // } else if (info.type == MediaType::VIDEO) { // qDebug() << "VIDEO PTS:" << info.pts; // } info.pts += info.type == MediaType::AUDIO ? info.encoder->GetCtx()->frame_size : 1; // 更新 pts AVPacket* packet = nullptr; while ((packet = info.encoder->Encode())) { av_packet_rescale_ts(packet, info.encoder->GetCtx()->time_base, info.stream->time_base); packet->stream_index = info.stream->index; if (av_interleaved_write_frame(_fmtCtx, packet) < 0) { return false; } } info.encoder->AfterEncode(); return true; } bool AvMuxer::_CheckTime(double time) { auto minTime = double(_infos.front().pts) / _infos.front().fps; for (int idx = 1; idx < _infos.size(); ++idx) { minTime = std::min(double(_infos[idx].pts) / _infos[idx].fps, minTime); } if (time - minTime > 0.3) { // 放宽到0.3秒 qDebug() << "丢帧: 当前流时间:" << time << "最慢流时间:" << minTime << "差值:" << (time - minTime); return false; } return true; } void AvMuxer::Close() { if (_fmtCtx == nullptr) { return; } // 清空编码器缓存 for (int index = 0; index < _infos.size(); ++index) { __DebugPrint("stream: %d, time:%f", index, double(_infos[index].pts) / _infos[index].fps); } if (_isOpenFile) { __CheckNo(av_write_trailer(_fmtCtx) >= 0); Free(_fmtCtx->pb, [this] { avio_closep(&_fmtCtx->pb); }); } _isOpenFile = false; for (auto&& info : _infos) { info.encoder->Close(); Free(info.encoder, [&info] {info.encoder->Close(); delete info.encoder; }); } _infos.clear(); Free(_fmtCtx, [this] { avformat_free_context(_fmtCtx); }); } bool AvMuxer::_AddStream(Info& info) { 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; 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; } AVCodecContext* AvMuxer::GetCodecCtx(int streamIndex) { __CheckNullptr(streamIndex >= 0 && _infos.size() > streamIndex); return _infos[streamIndex].encoder->GetCtx(); } bool AvMuxer::IsEncodeOverload() const { for (auto&& info : _infos) { if (info.isEncodeOverload) { return true; } } return false; }