zhuizhu 8 ay önce
ebeveyn
işleme
926c2dd503

+ 0 - 305
AV_REFACTOR_PLAN.md

@@ -1,305 +0,0 @@
-# AV项目重构方案
-
-## 项目概述
-
-当前有两个项目:
-- **AVPlayer2**: 音视频播放器,包含解码、播放功能
-- **AvRecorder**: 音视频录制器,包含采集、编码、推流功能
-
-## 重构目标
-
-1. **去Qt化**: 除UI渲染和音频播放外,其他模块不使用Qt
-2. **代码复用**: 提取两个项目的共同部分
-3. **模块化**: 清晰的模块划分,便于维护和扩展
-4. **小范围重构**: 每个步骤都能保证项目正常运行
-5. **统一日志**: 封装统一的日志系统
-
-## 新目录结构
-
-```
-AV/
-├── code/                    # 核心代码库(不依赖Qt)
-│   ├── base/               # 基础设施
-│   │   ├── logger.h/.cpp   # 统一日志系统
-│   │   ├── thread_base.h/.cpp # 线程基类
-│   │   ├── common.h/.cpp   # 通用工具
-│   │   └── types.h         # 类型定义
-│   ├── codec/              # 编解码模块
-│   │   ├── decoder/        # 解码器
-│   │   │   ├── audio_decoder.h/.cpp
-│   │   │   ├── video_decoder.h/.cpp
-│   │   │   └── decoder_base.h/.cpp
-│   │   └── encoder/        # 编码器
-│   │       ├── audio_encoder.h/.cpp
-│   │       ├── video_encoder.h/.cpp
-│   │       └── encoder_base.h/.cpp
-│   ├── capture/            # 采集模块
-│   │   ├── audio_capture.h/.cpp
-│   │   └── video_capture.h/.cpp
-│   ├── muxer/              # 复用器
-│   │   └── av_muxer.h/.cpp
-│   └── utils/              # 工具类
-│       ├── frame_queue.h/.cpp
-│       ├── packet_queue.h/.cpp
-│       └── sync_helper.h/.cpp
-├── AvPlayer/               # 播放器项目
-│   ├── ui/                 # Qt UI相关
-│   ├── player/             # 播放器逻辑
-│   └── main.cpp
-└── AvRecorder/             # 录制器项目
-    ├── ui/                 # Qt UI相关
-    ├── recorder/           # 录制器逻辑
-    └── main.cpp
-```
-
-## 重构步骤(分阶段进行)
-
-### 阶段1: 基础设施搭建(1-3步)
-
-#### 步骤1: 创建新目录结构和基础类
-- 创建 `AV/code/base/` 目录
-- 实现统一的日志系统 `logger.h/.cpp`
-- 实现线程基类 `thread_base.h/.cpp`(基于现有ThreadBase.h改进)
-- 实现通用类型定义 `types.h`
-
-#### 步骤2: 统一日志系统
-- 在两个项目中集成新的日志系统
-- 保持向后兼容,逐步替换现有日志调用
-- 测试日志功能正常
-
-#### 步骤3: 线程基类统一
-- 将AVPlayer2的ThreadBase迁移到新的thread_base
-- 在AvRecorder中也使用统一的线程基类
-- 确保所有线程相关功能正常
-
-### 阶段2: 编解码模块重构(4-8步)
-
-#### 步骤4: 抽象编解码基类
-- 创建 `AV/code/codec/` 目录
-- 基于AvRecorder的abstract_encoder设计统一的编解码基类
-- 定义编码器和解码器的通用接口
-
-#### 步骤5: 音频编码器重构
-- 将AvRecorder的audio_encoder迁移到新目录
-- 去除Qt依赖,使用标准C++
-- 在AvRecorder中使用新的音频编码器
-
-#### 步骤6: 视频编码器重构
-- 将AvRecorder的video_encoder迁移到新目录
-- 去除Qt依赖
-- 在AvRecorder中使用新的视频编码器
-
-#### 步骤7: 音频解码器重构
-- 基于AVPlayer2的audio_decode_thread创建新的audio_decoder
-- 分离解码逻辑和线程逻辑
-- 在AVPlayer2中使用新的音频解码器
-
-#### 步骤8: 视频解码器重构
-- 基于AVPlayer2的video_decode_thread创建新的video_decoder
-- 分离解码逻辑和线程逻辑
-- 在AVPlayer2中使用新的视频解码器
-
-### 阶段3: 采集和复用模块(9-11步)
-
-#### 步骤9: 音视频采集模块
-- 将AvRecorder的capturer模块迁移到 `AV/code/capture/`
-- 去除Qt依赖(除了必要的音频采集部分)
-- 统一采集接口
-
-#### 步骤10: 复用器模块
-- 将AvRecorder的muxer迁移到新目录
-- 确保与新的编码器兼容
-
-#### 步骤11: 工具类模块
-- 创建统一的队列管理(frame_queue, packet_queue)
-- 创建同步辅助类
-- 替换两个项目中的相关实现
-
-### 阶段4: 项目整合和优化(12-15步)
-
-#### 步骤12: AVPlayer2重构
-- 使用新的核心库重构播放器
-- 保持UI部分不变
-- 确保播放功能完整
-
-#### 步骤13: AvRecorder重构
-- 使用新的核心库重构录制器
-- 保持UI部分不变
-- 确保录制和推流功能完整
-
-#### 步骤14: 代码清理
-- 删除重复的旧代码
-- 优化包含路径
-- 统一编码规范
-
-#### 步骤15: 测试和文档
-- 全面测试两个项目的功能
-- 编写使用文档
-- 性能优化
-
-## 技术要点
-
-### 日志系统设计
-```cpp
-// 支持Qt的qDebug等,但封装为独立接口
-class Logger {
-public:
-    static Logger& instance();
-    void debug(const std::string& msg);
-    void info(const std::string& msg);
-    void warning(const std::string& msg);
-    void error(const std::string& msg);
-    
-    // Qt兼容接口
-    void qtDebug(const QString& msg);
-    void qtInfo(const QString& msg);
-};
-
-// 宏定义,便于使用
-#define LOG_DEBUG(msg) Logger::instance().debug(msg)
-#define LOG_INFO(msg) Logger::instance().info(msg)
-```
-
-### 线程基类改进
-- 基于现有ThreadBase.h,但去除Qt依赖
-- 使用std::thread和标准库同步原语
-- 提供更好的异常处理和资源管理
-
-### 编解码接口设计
-```cpp
-class CodecBase {
-public:
-    virtual bool open(const CodecParams& params) = 0;
-    virtual bool process(AVFrame* frame) = 0;
-    virtual void close() = 0;
-    virtual ~CodecBase() = default;
-};
-
-class EncoderBase : public CodecBase {
-public:
-    virtual AVPacket* encode(AVFrame* frame) = 0;
-};
-
-class DecoderBase : public CodecBase {
-public:
-    virtual AVFrame* decode(AVPacket* packet) = 0;
-};
-```
-
-## 风险控制
-
-1. **每个步骤都要测试**: 确保修改后项目能正常编译和运行
-2. **保持向后兼容**: 在过渡期间保留旧接口
-3. **分支开发**: 在独立分支进行重构,主分支保持稳定
-4. **增量迁移**: 逐个模块迁移,不要一次性大改
-5. **回滚机制**: 每个步骤完成后提交代码,便于回滚
-
-## 当前进度
-
-### 第一阶段:基础设施 ✅
-- [x] 创建基础目录结构 (`AV/code/`)
-- [x] 实现统一日志系统 (`logger.h/cpp`)
-- [x] 实现线程基类 (`thread_base.h/cpp`)
-- [x] 实现通用工具类 (`common.h/cpp`, `types.h`)
-- [x] 创建基础测试程序 (`test_basic.cpp`)
-
-### 第二阶段:编解码模块 ✅
-- [x] 编解码抽象基类设计 (`abstract_codec.h/cpp`)
-- [x] 视频编码器实现 (`video_encoder.h/cpp`)
-  - [x] 硬件加速支持 (NVENC, QSV, AMF, VideoToolbox)
-  - [x] 像素格式转换
-  - [x] 编码参数配置
-  - [x] 统计信息收集
-- [x] 音频编码器实现 (`audio_encoder.h/cpp`)
-  - [x] 音频重采样支持
-  - [x] 多种音频格式支持 (AAC, MP3, Opus, FLAC等)
-  - [x] 声道布局处理
-  - [x] 采样率转换
-- [x] 编解码工厂模式
-- [x] 编解码测试程序 (`test_codec.cpp`)
-- [x] CMake构建系统配置
-
-### 第三阶段:解码器模块 ✅
-- [x] 视频解码器实现 (`codec_video_decoder.h/cpp`)
-  - [x] 硬件加速支持检测
-  - [x] 多种视频格式支持 (H.264, H.265, VP8, VP9等)
-  - [x] 像素格式转换
-  - [x] 低延迟模式
-  - [x] 统计信息收集
-- [x] 音频解码器实现 (`codec_audio_decoder.h/cpp`)
-  - [x] 音频重采样支持
-  - [x] 多种音频格式支持 (AAC, MP3, Opus, FLAC等)
-  - [x] 声道布局处理
-  - [x] 采样率转换
-- [x] 解码器测试程序 (`test_decoder.cpp`)
-
-### 第四阶段:采集和复用模块 ✅
-- [x] 视频采集模块 (`capture_video_capturer.h/cpp`)
-  - [x] 屏幕采集支持
-  - [x] 窗口采集支持
-  - [x] 摄像头采集支持
-- [x] 音频采集模块 (`capture_audio_capturer.h/cpp`)
-  - [x] 系统音频采集
-  - [x] 麦克风采集
-  - [x] 音频混合支持
-- [x] 媒体复用器模块 (`muxer_*.h/cpp`)
-  - [x] 文件复用器 (MP4, AVI, MKV等)
-  - [x] 流媒体复用器 (RTMP, HLS等)
-  - [x] 分段录制支持
-- [x] 工具类模块 (`utils_*.h/cpp`)
-  - [x] 帧队列管理
-  - [x] 数据包队列管理
-  - [x] 同步辅助类
-  - [x] 性能监控
-  - [x] 线程池管理
-
-### 第五阶段:播放器核心模块 ✅
-- [x] 播放器核心类实现 (`player_core.h/cpp`)
-  - [x] 媒体文件打开和解析
-  - [x] 播放控制接口 (播放、暂停、停止、跳转)
-  - [x] 音视频同步
-  - [x] 播放速度控制
-  - [x] 音量控制
-  - [x] 播放状态管理
-  - [x] 事件回调机制
-- [x] 队列管理模块 (`queue_manager.h/cpp`)
-  - [x] 数据包队列 (PacketQueue)
-  - [x] 帧队列 (FrameQueue)
-  - [x] 音视频同步器 (Synchronizer)
-  - [x] 线程安全设计
-- [x] 线程管理模块 (`thread_manager.h/cpp`)
-  - [x] 读取线程 (ReadThread)
-  - [x] 视频解码线程 (VideoDecodeThread)
-  - [x] 音频解码线程 (AudioDecodeThread)
-  - [x] 线程管理器 (ThreadManager)
-- [x] Qt集成适配器 (`player_adapter.h/cpp`)
-  - [x] PlayerAdapter类设计和实现
-  - [x] Qt信号槽接口
-  - [x] 线程安全的事件回调
-  - [x] PlayerAdapterFactory工厂类
-- [x] 播放器测试程序 (`test_player.cpp`)
-- [x] 集成示例和文档
-  - [x] integration_example.cpp集成示例
-  - [x] INTEGRATION_GUIDE.md集成指南
-  - [x] API对比和迁移指南
-  - [x] 完整的UI示例实现
-- [x] CMake构建系统配置
-
-### 第六阶段:项目整合和优化 🔄
-- [ ] AVPlayer2重构 (使用新的核心库)
-- [ ] AvRecorder重构 (使用新的核心库)
-- [ ] 性能优化
-- [ ] 文档完善
-- [ ] 单元测试
-
-## 预期收益
-
-1. **代码复用**: 减少重复代码,提高维护效率
-2. **模块化**: 清晰的模块边界,便于测试和扩展
-3. **跨平台**: 核心逻辑不依赖Qt,便于移植
-4. **性能优化**: 去除不必要的Qt开销
-5. **团队协作**: 统一的代码规范和架构
-
-## 下一步行动
-
-建议从**步骤1**开始,创建基础目录结构和日志系统。每完成一个步骤,都要进行充分测试,确保项目功能正常后再进行下一步。

+ 22 - 3
AvRecorder/encoder/audio_encoder.cpp

@@ -1,12 +1,31 @@
 #include "audio_encoder.h"
 
-bool Encoder<MediaType::AUDIO>::Open(const Param& audioParma, AVFormatContext* fmtCtx)
+#include <QDebug>
+
+bool Encoder<MediaType::AUDIO>::Open(const Param& audioParam, AVFormatContext* fmtCtx)
 {
     Close();
     _isOpen = false;
-    __CheckBool(_Init(audioParma, fmtCtx));
-    __CheckBool(avcodec_open2(_codecCtx, _codec, nullptr) >= 0);
+    
+    qDebug() << "AudioEncoder::Open: Initializing audio encoder with bitrate" << audioParam.bitRate;
+    
+    if (!_Init(audioParam, fmtCtx)) {
+        qDebug() << "AudioEncoder::Open failed: Audio encoder initialization failed";
+        return false;
+    }
+    
+    int ret = avcodec_open2(_codecCtx, _codec, nullptr);
+    if (ret < 0) {
+        char errbuf[AV_ERROR_MAX_STRING_SIZE];
+        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
+        qDebug() << "AudioEncoder::Open failed: Cannot open audio codec. Error:" << errbuf << "(" << ret << ")";
+        qDebug() << "Codec name:" << _codec->name << "ID:" << _codec->id;
+        qDebug() << "Sample rate:" << _codecCtx->sample_rate << "Channels:" << _codecCtx->ch_layout.nb_channels;
+        return false;
+    }
+    
     _isOpen = true;
+    qDebug() << "AudioEncoder::Open: Audio encoder opened successfully";
     return true;
 }
 void Encoder<MediaType::AUDIO>::Close()

+ 4 - 4
AvRecorder/encoder/audio_mixer.cpp

@@ -175,10 +175,10 @@ AVFrame* AudioMixer::Convert(uint32_t index, uint8_t* inBuf, uint32_t size)
     __CheckNullptr(iter != _audioInputInfos.end());
 
     // // 添加调试信息
-    static int debugCounter = 0;
-    if (++debugCounter % 100 == 0) {
-        qDebug() << "AudioMixer::Convert - Input size:" << size << "bytes, Input index: " << index;
-    }
+    // static int debugCounter = 0;
+    // if (++debugCounter % 100 == 0) {
+    //     qDebug() << "AudioMixer::Convert - Input size:" << size << "bytes, Input index: " << index;
+    // }
 
     __CheckNullptr(iter->second.resampler->Convert(inBuf, size));
     return _AdjustVolume() ? _outputFrame : nullptr;

+ 20 - 2
AvRecorder/encoder/video_encoder.cpp

@@ -4,6 +4,8 @@ extern "C" {
 #include <libavutil/opt.h>
 }
 
+#include <QDebug>
+
 std::vector<std::string> Encoder<MediaType::VIDEO>::_usableEncoders;
 
 Encoder<MediaType::VIDEO>::Encoder() {}
@@ -12,12 +14,28 @@ bool Encoder<MediaType::VIDEO>::Open(const Param& encodeParam, AVFormatContext*
 {
     Close();
     _isOpen = false;
-    __CheckBool(_Init(encodeParam, fmtCtx));
+    
+    qDebug() << "VideoEncoder::Open: Initializing video encoder with codec" << QString::fromStdString(encodeParam.name)
+             << "resolution" << encodeParam.width << "x" << encodeParam.height
+             << "fps" << encodeParam.fps << "bitrate" << encodeParam.bitRate;
+    
+    if (!_Init(encodeParam, fmtCtx)) {
+        qDebug() << "VideoEncoder::Open failed: Encoder initialization failed";
+        return false;
+    }
 
     // 打开编码器
-    __CheckBool(avcodec_open2(_codecCtx, _codec, nullptr) >= 0);
+    int ret = avcodec_open2(_codecCtx, _codec, nullptr);
+    if (ret < 0) {
+        char errbuf[AV_ERROR_MAX_STRING_SIZE];
+        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
+        qDebug() << "VideoEncoder::Open failed: Cannot open codec. Error:" << errbuf << "(" << ret << ")";
+        qDebug() << "Codec name:" << _codec->name << "ID:" << _codec->id;
+        return false;
+    }
 
     _isOpen = true;
+    qDebug() << "VideoEncoder::Open: Video encoder opened successfully";
     return true;
 }
 

+ 227 - 15
AvRecorder/muxer/av_muxer.cpp

@@ -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;
 }
 

+ 26 - 8
AvRecorder/ui/av_recorder.cpp

@@ -14,8 +14,8 @@ AvRecorder::AvRecorder(QWidget* parent)
     m_settingsParam.videoParam.fps = 30;
     m_settingsParam.videoParam.name = Encoder<MediaType::VIDEO>::GetUsableEncoders().front();
     m_settingsParam.outputDir = ".";
-    // m_settingsParam.liveUrl = "rtmp://192.168.3.76:1935/stream/V1";
-    // m_settingsParam.liveName = "stream";
+    m_settingsParam.liveUrl = "rtmp://127.0.0.1:1935/stream/V1";
+    m_settingsParam.liveName = "stream";
 
     // 1. 视频预览区
     m_glWidget = new OpenGLVideoWidget(this);
@@ -378,12 +378,30 @@ void AvRecorder::stopPreview()
 
 bool AvRecorder::startStream(std::string_view path, std::string_view format)
 {
-    __CheckBool(m_avMuxer.Open(path, format));
-    __CheckBool(m_audioRecorder.LoadMuxer(m_avMuxer));
-    __CheckBool(m_videoRecorder.LoadMuxer(m_avMuxer));
-    __CheckBool(m_avMuxer.WriteHeader());
-    __CheckBool(m_audioRecorder.StartRecord());
-    __CheckBool(m_videoRecorder.StartRecord());
+    if (!m_avMuxer.Open(path, format)) {
+        qDebug() << "Failed to open muxer with path:" << QString::fromStdString(std::string(path)) << "format:" << QString::fromStdString(std::string(format));
+        return false;
+    }
+    if (!m_audioRecorder.LoadMuxer(m_avMuxer)) {
+        qDebug() << "Failed to load muxer for audio recorder";
+        return false;
+    }
+    if (!m_videoRecorder.LoadMuxer(m_avMuxer)) {
+        qDebug() << "Failed to load muxer for video recorder";
+        return false;
+    }
+    if (!m_avMuxer.WriteHeader()) {
+        qDebug() << "Failed to write muxer header";
+        return false;
+    }
+    if (!m_audioRecorder.StartRecord()) {
+        qDebug() << "Failed to start audio recording";
+        return false;
+    }
+    if (!m_videoRecorder.StartRecord()) {
+        qDebug() << "Failed to start video recording";
+        return false;
+    }
     m_recordTime = QTime::currentTime();
     m_captureStatusLabel->setText("状态: 正在工作");
     m_settingsBtn->setEnabled(false);