使用FFmpeg库实现 本地和rtp 音视频播放器,使用qt绘制视频。
本demo环境为 qt5.12 vs2019-32位 .pro的qt工程
FFmpeg版本位3.4.8 vs2092-32位
本demo一共分为四部分
1:FFmpeg接口类,封装了一些FFmpeg的api,方便提供调用
2:thread类, 线程类,读取视频,解码,发送数据
3:UI类 使用qt的QOpenGLWidget类来绘制图像
4:audio播放类。 使用qt的QAudioOutput来播放解码后的音频数据
初始化ffmpeg
avcodec_register_all();//注册所有解码器
av_register_all();//注册所有格式
avformat_network_init();//初始化网络流格式,使用RTSP网络流时必须先执行
m_avFormatContext = avformat_alloc_context();
m_yuvFrame = av_frame_alloc();
m_pcmFrame = av_frame_alloc();
打开视频流或者rtp,流获取视频信息
//打开视频流
int result=avformat_open_input(&m_avFormatContext, url.toStdString().c_str(),nullptr,nullptr);
if (result<0)
{
qDebug()<<"avformat_open_input error ---"<<result;
return false;
}
//获取视频流信息
result=avformat_find_stream_info(m_avFormatContext,nullptr);
if (result<0)
{
qDebug()<<"avformat_find_stream_info error ---"<<result;
return false;
}
初始化音频相关,需要获取音频流的索引,初始化解码器上下文,音频重采样上下文
//获取音频流索引
for (uint i = 0; i < m_avFormatContext->nb_streams; i++)
{
if (AVMEDIA_TYPE_AUDIO == m_avFormatContext->streams[i]->codec->codec_type)
{
m_audioCodecContext = m_avFormatContext->streams[i]->codec;
m_audioStreamIndex = i;
break;
}
}
if (-1 == m_audioStreamIndex)
{
return false;
}
if(nullptr == m_audioCodecContext)
{
return false;
}
//初始化一个视音频编解码器的AVCodecContext
AVCodec *codec = avcodec_find_decoder(m_audioCodecContext->codec_id);//查找解码器
if (avcodec_open2(m_audioCodecContext, codec, nullptr) < 0)
{
qDebug()<<"avcodec_open2 error---";
return false;
}
m_sampleRate = m_audioCodecContext->sample_rate;//样本率
m_channel = m_audioCodecContext->channels;//通道数
switch (m_audioCodecContext->sample_fmt)//样本大小
{
case AV_SAMPLE_FMT_S16:
this->m_sampleSize = 16;
break;
case AV_SAMPLE_FMT_S32:
this->m_sampleSize = 32;
default:
break;
}
if (nullptr == m_audioSwrContext)
{
m_audioSwrContext = swr_alloc();//初始化
swr_alloc_set_opts(m_audioSwrContext,m_audioCodecContext->channel_layout,
AV_SAMPLE_FMT_S16,
m_audioCodecContext->sample_rate,
m_audioCodecContext->channels,
m_audioCodecContext->sample_fmt,
m_audioCodecContext->sample_rate,
0,0
);
swr_init(m_audioSwrContext);
}
初始化视频相关,获取视频流的索引,初始化解码器上下文。
//获取视频流索引
for (uint i = 0; i < m_avFormatContext->nb_streams; i++)
{
if (AVMEDIA_TYPE_VIDEO == m_avFormatContext->streams[i]->codec->codec_type)
{
m_videoStreamIndex = i;
m_videoCodecContext = m_avFormatContext->streams[i]->codec;
break;
}
}
if(-1 == m_videoStreamIndex)
{
qDebug()<<"videoStreamIndex init error---";
return false;
}
if (m_videoCodecContext == nullptr)
{
qDebug()<<"videoCodecContext init error---";
return false;
}
//获取视频流解码器
AVCodec *pAVCodec = avcodec_find_decoder(m_videoCodecContext->codec_id);
//打开对应解码器
int result=avcodec_open2(m_videoCodecContext,pAVCodec,nullptr);
if (result<0)
{
qDebug()<<"avcodec_open2 video open error";
return false;
}
m_videoSwsContext = sws_getContext(m_videoCodecContext->width,m_videoCodecContext->height,
m_videoCodecContext->pix_fmt,
m_videoCodecContext->width,m_videoCodecContext->height,
AV_PIX_FMT_BGRA,SWS_BICUBIC,0,0,0);
avpicture_alloc(&pAVPicture,AV_PIX_FMT_BGRA,m_videoCodecContext->width,m_videoCodecContext->height);
接下来就是线程run里面的读取数据,解码,发送数据的操作
void VideoThread::run()
{
char audioOut[10000] = {0};
while(m_isRun)
{
int free = AudioPlayer::Get()->GetFree();
if (free < 10000)
{
msleep(1);
continue;
}
AVPacket pkt = m_ffmpeg->getPacket();
if (pkt.size <= 0)
{
msleep(10);
continue;
}
if (pkt.stream_index == m_ffmpeg->m_audioStreamIndex)
{
m_ffmpeg->decode(&pkt);//解码
int len = m_ffmpeg->getPCM(audioOut);//获取一帧音频的pcm
AudioPlayer::Get()->Write(audioOut, len);
}
else
{
if(m_ffmpeg->decode(&pkt))
{
m_ffmpeg->getRBG();
}
}
av_packet_unref(&pkt);
}
}
AudioPlayer::Get()->Write(audioOut, len); 此部分是音频播放
m_ffmpeg->getRBG(); 此部分是获取rgb数据帧, 并且绑定信号跟槽函数到QTopenglWidget绘制
UI绘制上:
在槽函数中接受数据,并且updata刷新
void OpenglWidget::paintEvent(QPaintEvent *e)
{
QPainter painter;
painter.begin(this);
painter.drawImage(QPoint(0, 0), m_image);
painter.end();
}
void OpenglWidget::showImage(const QImage &image)
{
if(image.width() > image.height())
m_image = image.scaledToWidth(width(),Qt::SmoothTransformation);
else
m_image = image.scaledToHeight(height(),Qt::SmoothTransformation);
update();
}
绘制是按照视频原有的比例来绘制, 如果大分辨率切成小分辨率则需要刷新一下背景
以上是在做音视频demo时候的一点小心得,第一次发表,如有不妥之处,敬请指教。
demo链接 https://download.csdn.net/download/qq871580236/23535111