Qt FFmpeg 音视频播放器

使用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;
    }

初始化音频相关,需要获取音频流的索引,初始化解码器上下文,音频重采样上下文

扫描二维码关注公众号,回复: 14846698 查看本文章
    //获取音频流索引
    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

        

猜你喜欢

转载自blog.csdn.net/qq871580236/article/details/120364013