Qt+FFmpeg+OpenGL实现视频播放器(2)

1、用Qlabel播放视频

从解码后的数据中提取单个视频帧,并转换为 RGB 格式以便在 QLabel 上显示。使用了 QTimer 来控制帧的播放;

VideoInfo 类中,QTimer 用于定时触发视频帧的解码和渲染。QTimer 的工作机制是通过设定一个时间间隔(以毫秒为单位),然后周期性地发出 timeout() 信号。在每次 timeout() 信号触发时,执行特定的槽函数来处理视频解码和帧显示。

开始和停止播放
void VideoInfo::startPlayback()
{
    timer->start(1000 / 30); // 假设帧率为30帧每秒
}

void VideoInfo::stopPlayback()
{
    timer->stop();
}
  • startPlayback(): 这个函数启动计时器,并设置时间间隔为 1000 / 30 毫秒(约 33.3 毫秒),对应每秒 30 帧的帧率。这种方式确保每秒调用 decodeAndDisplayFrame() 函数 30 次,从而达到播放视频的效果。

  • stopPlayback(): 这个函数停止计时器,从而停止调用 decodeAndDisplayFrame()

解码和显示帧
void VideoInfo::decodeAndDisplayFrame()
{
    QImage frameImage = getFrame();
    if (!frameImage.isNull()) {
        emit frameReady(frameImage);
    } else {
        stopPlayback();
    }
}
  • decodeAndDisplayFrame(): 这个槽函数在每次计时器超时时调用。它调用 getFrame() 函数尝试获取一个视频帧的图像数据。

  • 如果成功获取到图像(frameImage 不是空的),则通过发射 frameReady(frameImage) 信号将该帧传递给 Player 类来显示。

  • 如果获取图像失败(frameImage 是空的),则调用 stopPlayback() 停止播放。

通过使用 QTimer,项目可以实现视频帧的定时解码和显示。这种方法使得视频播放逻辑相对简单,只需处理定时触发的解码和显示操作即可。定时器的使用允许在不同的帧率下播放视频,并且可以轻松控制播放和暂停的状态。

运行结果如下:

2、增加音频播放功能

查找音频流:在 openFile 函数中,加入了查找音频流的代码,通过遍历 formatContextstreams 来查找音频流的索引,并存储在 audioStreamIndex 中。

audioStreamIndex = -1;
for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
    if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioStreamIndex = i;
        break;
    }
}

设置音频解码器:在找到音频流后,接下来要初始化音频解码器上下文

AVCodecParameters *audioCodecParameters = formatContext->streams[audioStreamIndex]->codecpar;
const AVCodec *audioCodec = avcodec_find_decoder(audioCodecParameters->codec_id);
audioCodecContext = avcodec_alloc_context3(audioCodec);
avcodec_parameters_to_context(audioCodecContext, audioCodecParameters);
avcodec_open2(audioCodecContext, audioCodec, nullptr);

设置音频输出:使用 Qt 的 QAudioSink 类来处理音频输出(QT6中已经取代QAudioOutput

QAudioFormat format;
format.setSampleRate(48000);       // 设置采样率
format.setChannelCount(2);         // 设置声道数
format.setSampleFormat(QAudioFormat::Float); // 使用浮点数格式

audioSink = new QAudioSink(QMediaDevices::defaultAudioOutput(), format, this);
audioBuffer = new QBuffer(this);
audioBuffer->open(QIODevice::ReadWrite);

在这里,音频格式被设置为 48kHz 的采样率,立体声,使用浮点数格式。audioSink 是实际的音频输出设备,audioBuffer 则是用于存储和播放音频数据的缓冲区。

播放控制:startPlayback 函数中,启动音频播放

if (audioSink) {
    audioSink->start(audioBuffer);
}

解码并处理音频数据:processAudioData 函数中,通过以下步骤来解码和播放音频数据:

if (av_read_frame(formatContext, packet) >= 0) {
    if (packet->stream_index == audioStreamIndex) {
        int response = avcodec_send_packet(audioCodecContext, packet);
        if (response < 0) {
            // 处理错误
            av_packet_unref(packet);
            return;
        }

        while (avcodec_receive_frame(audioCodecContext, audioFrame) >= 0) {
            int channels = audioCodecContext->ch_layout.nb_channels;
            int dataSize = av_samples_get_buffer_size(nullptr, channels,
                                                      audioFrame->nb_samples, audioCodecContext->sample_fmt, 1);

            if (dataSize > 0) {
                audioBuffer->write(reinterpret_cast<const char*>(audioFrame->data[0]), dataSize);
                audioBuffer->seek(0); // 确保播放从开始位置开始
            }
        }
    }
    av_packet_unref(packet);
}

这些代码实现了以下逻辑:

  1. 从文件中读取一个包(packet)。
  2. 检查这个包是否属于音频流(通过 packet->stream_index == audioStreamIndex 判断)。
  3. 将音频包发送给解码器,并检查是否有解码后的帧可用。
  4. 将解码后的音频数据写入 audioBuffer,供 QAudioSink 播放。

通过查找音频流、初始化音频解码器、配置音频输出设备,以及通过定时器不断解码音频数据并写入缓冲区,实现了音频的解码和播放。这些步骤与视频解码类似,但针对音频的特殊处理包括设置音频格式、使用 QAudioSink 进行播放以及音频数据的缓冲管理。通过这些实现,程序可以同时播放视频和音频,实现一个简单的媒体播放器功能。但是为了更流畅的播放体验,可以使用多线程来处理视频和音频的解码与播放。使用多线程有助于避免主线程阻塞,从而提高应用的响应性。下一节将指导如何使用多线程来处理音频和视频解码,并播放音频和渲染视频。

  • 13
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值