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
函数中,加入了查找音频流的代码,通过遍历 formatContext
的 streams
来查找音频流的索引,并存储在 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);
}
这些代码实现了以下逻辑:
- 从文件中读取一个包(packet)。
- 检查这个包是否属于音频流(通过
packet->stream_index == audioStreamIndex
判断)。 - 将音频包发送给解码器,并检查是否有解码后的帧可用。
- 将解码后的音频数据写入
audioBuffer
,供QAudioSink
播放。
通过查找音频流、初始化音频解码器、配置音频输出设备,以及通过定时器不断解码音频数据并写入缓冲区,实现了音频的解码和播放。这些步骤与视频解码类似,但针对音频的特殊处理包括设置音频格式、使用 QAudioSink
进行播放以及音频数据的缓冲管理。通过这些实现,程序可以同时播放视频和音频,实现一个简单的媒体播放器功能。但是为了更流畅的播放体验,可以使用多线程来处理视频和音频的解码与播放。使用多线程有助于避免主线程阻塞,从而提高应用的响应性。下一节将指导如何使用多线程来处理音频和视频解码,并播放音频和渲染视频。