Qt ffmpeg解码av_read_frame 实现暂停功能

本文介绍了在视频解码过程中,如何通过线程和条件变量实现暂停与时间校准,重点讲解了使用QWaitCondition处理暂停期间的时间调整以及帧发送策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解码的时候都是通过线程while读出视频流
然后在转为QImage发送出去
暂停就是把线程给暂停
通过QWaitCondition 或是 std::condition_variable(例子用的是std::condition_variable)
因为是通过帧的时间戳来发送QImage
在计算时间戳的时候,需要把暂停这段时间给减上就行了

// 记录开始读视频流的时间戳
qint64 start_ms = QDateTime::currentDateTime().toMSecsSinceEpoch();
// 暂停时长时间戳
qint64 wait_ms = 0;
// 当前时间减去start_ms
// 判断是否大于dts时间戳
// 小于就等待
// 大于就开始读下一帧
qint64 msec = 0;
status = VideoProcess::__running;

// 枚举类型
// __stop = 0
// __running = 1
// __pause = 2
while (status > VideoProcess::__stop)
{
    res = av_read_frame(decode->fmtCnt, &decode->packet);
    if( res < 0 ) {
    	// 解码完成 退出循环
        status = VideoProcess::__stop;
        break;
    }
    if( decode->packet.stream_index == decode->video_index )
    {
        res = avcodec_send_packet(decode->codecCnt, &decode->packet);
        if( res < 0 ) {
            continue;
        }
        res = avcodec_receive_frame(decode->codecCnt, decode->frame);
        if( res < 0 ) {
            continue;
        }

		// 获取帧的时间戳
        int64_t cur_dts = decode->packet.dts;
        int64_t cur_dts_ms = cur_dts * av_q2d(decode->stream->time_base) * 1000;

        sws_scale(decode->sws,
                  decode->frame->data, decode->frame->linesize, 0, decode->frame->height,
                  decode->rgbFrame->data, decode->rgbFrame->linesize);

        // 暂停视频
        if( status == VideoProcess::__pause )
        {
        	// 发送暂停信号
        	// 只要在其他线程调用condition.notify_all()
        	// 就可以解除暂停
        	emit f->statusChanged();
            qint64 wait_start_ms = QDateTime::currentDateTime().toMSecsSinceEpoch();
            std::unique_lock<std::mutex> lock(mutex);
            condition.wait(lock);
            lock.unlock();
            wait_ms = wait_ms + (QDateTime::currentDateTime().toMSecsSinceEpoch() - wait_start_ms);
            // 发送播放信号
            status = __VideoProcess::__play
            emit f->statusChanged();
        }
        // 发送帧图片
        emit f->frame(img);
        
        // 延时
        while (1)
        {
            msec = QDateTime::currentDateTime().toMSecsSinceEpoch() - start_ms - wait_ms;
            if( msec > cur_dts_ms ) {
                break;
            }
            decode->current_time = msec / 1000.0;
        }
    }
}
### 使用 QtFFmpeg 解码 uint16 Raw 格式的视频 为了使用 QtFFmpeg解码 `uint16` 原始格式的视频文件,可以遵循以下方法。此过程涉及配置项目环境、编写必要的 C++ 代码来初始化 FFMpeg 库并读取帧数据。 #### 配置项目环境 在 `.pro` 文件中加入特定路径以便访问静态库和头文件[^2]: ```makefile TEMPLATE = app CONFIG += console c++11 CONFIG -= app_bundle CONFIG -= qt SOURCES += main.cpp decode_video.cpp INCLUDEPATH += /path/to/ffmpeg/include LIBS += -L/path/to/ffmpeg/lib \ -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lswresample -lswscale ``` 请注意替换 `/path/to/ffmpeg/...` 为实际安装目录下的相应子目录。 #### 初始化 FFMpeg 并打开输入文件 创建一个新的源文件用于编码逻辑,在其中包含必要的头文件,并通过 `av_register_all()` 注册所有可用组件: ```cpp extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } int init_ffmpeg() { av_register_all(); return 0; } ``` 接着尝试加载目标媒体资源作为输入流: ```cpp AVFormatContext *fmt_ctx = nullptr; if (avformat_open_input(&fmt_ctx, filename.c_str(), NULL, NULL) != 0){ // 打开失败处理... return -1; } if(avformat_find_stream_info(fmt_ctx, NULL)<0){ // 获取流信息失败处理... return -1; } ``` #### 查找视频流索引及分配上下文空间 遍历找到第一个匹配类型的流(这里假设只关心视频),同时准备解码器实例及其私有数据结构体: ```cpp int videoStreamIndex=-1; for(unsigned int i=0; i<fmt_ctx->nb_streams;i++){ if(fmt_ctx->streams[i]->codecpar->codec_type== AVMEDIA_TYPE_VIDEO){ videoStreamIndex=i; break; } } if(videoStreamIndex==-1){ // 没有发现合适的视频轨道时返回错误. return -1; } const AVCodecParameters* codec_params = fmt_ctx->streams[videoStreamIndex]->codecpar; AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id); if (!decoder || avcodec_parameters_to_context(decoderCtx, codec_params) < 0 || avcodec_open2(decoderCtx, decoder, NULL) < 0){ // 创建或开启解码器失败处理... return -1; } ``` 对于 `uint16` 的原始像素格式(`raw`),通常意味着每两个字节表示一个亮度样本值(灰度级),因此需要特别注意设置正确的色彩空间参数. #### 循环读取包并送入解码队列 一旦成功设置了上述条件,则可以通过循环调用 `av_read_frame()` 函数获取压缩后的NALU单元或其他封装形式的数据包(Packets),再传递给对应的解码函数进行进一步解析成完整的图像帧(Frame): ```cpp while(true){ AVPacket packet; if(int ret = av_read_frame(fmt_ctx,&packet)>=0 && packet.stream_index == videoStreamIndex){ if(int sendRet = avcodec_send_packet(decoderCtx,&packet)==0){ while(int recvRet = avcodec_receive_frame(decoderCtx,pFrame)>=0){ // 成功接收到一帧后在此处添加渲染逻辑 // 清理当前接收完毕的对象 av_frame_unref(pFrame); } }else{ // 发送分组至解码器过程中出现问题 } // 不论是否正常结束都应当释放临时使用的内存区域 av_packet_unref(&packet); continue; }else{ // 当到达文件结尾或者其他异常情况发生时退出循环 break; } } ``` 最后一步就是根据实际情况调整显示方式了——这取决于具体应用场景下希望怎样呈现这些经过解压还原出来的视觉内容。如果是在基于Qt的应用程序内部操作的话,那么很可能还需要借助于QWidget类族中的某些成员比如QImage/QPixmap等完成最终的画面展现工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值