ffmpeg解封装mp4并解码渲染(SDL2)

一、概述

  在前几篇的文章中介绍了从H264文件中读取数据封装成AVPacket并通过解码器解码,然后用SDL进行渲染的案例。本节继上面的内容,通过加载一个mp4文件,通过ffmpeg的解封装功能

,把AVPacket拿出来,然后放到之前封装好的解码器中进行解码,然后使用SDL进行渲染操作。

  ps:本节的重点是解封装

二、代码示例

  1.初始化解封装上下文,并打开媒体文件

//打开文件流
AVFormatContext* ic = nullptr;
int ret = avformat_open_input(&ic, this->fileName.toStdString().c_str(),
    NULL,//封装器格式 null 自动探测 根据后缀名或者文件头
    NULL);//参数设置,rtsp需要设置
if (ret != 0)
{
    qDebug() << "avformat_open_input failed!打开解封装上下文失败";
    return;
}

 

  2.发现媒体信息:avformat_find_stream_info

//发现媒体流信息
ret = avformat_find_stream_info(ic, NULL);
if (ret < 0)
{
    qDebug() << "avformat_find_stream_info failed!";
    return;
}

 

  3.分别找到音频流和视频流

//找到音视频流
AVStream* vs = nullptr;
AVStream* as = nullptr;
for (int i = 0;i < ic->nb_streams;i++)
{
    //视频
    if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
    {
        vs = ic->streams[i];
    }
    else if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//音频
    {
        as = ic->streams[i];
    }
}

 

  4.拿到视频的宽高

int videoWidth = vs->codecpar->width;
int videoHeight = vs->codecpar->height;

 

  5.通过Qt的信号槽,把视频的宽高发射出去,Qt的槽函数会接收,并更新窗口和label的大小。ps:由于解封装和解码操作都是在子线程中工作,而子线程中没办法直接更新Qt的UI操作。

    • 初始化信号槽
      signals:
          void updateSize(int videoWidth, int videoHeight);
      
      public slots:
          void onUpdateSize(int videoWidth, int videoHeight);

       

    • 绑定信号槽
      connect(this, &DeMuxerAndPlayWindow::updateSize, this, &DeMuxerAndPlayWindow::onUpdateSize, Qt::QueuedConnection);

       

    • 发送信号函数
      int videoWidth = vs->codecpar->width;
      int videoHeight = vs->codecpar->height;
      emit updateSize(videoWidth, videoHeight);//发送信号到槽函数

       

    • 通过槽函数接收,并更新UI
      void DeMuxerAndPlayWindow::onUpdateSize(int videoWidth, int videoHeight)
      {
          ui.labelVideo->move(0, 0);
          ui.labelVideo->resize(QSize(videoWidth, videoHeight));
          resize(QSize(videoWidth, videoHeight));
          renderView = IVideoRenderView::Create();
          renderView->Init(videoWidth, videoHeight, IVideoRenderView::YUV420P, (void*)ui.labelVideo->winId());
      }

       

  6.创建解码器,并初始化参数,此处特别需要注意:需要把解封装上下文的参数赋值给解码器上下文,这样解码器就等于拿到了视频的基础参数,如:宽、高、像素格式等。

//创建一个解码器
YDecoder* decoder = new YDecoder();
AVCodecContext* context = decoder->Create(vs->codecpar->codec_id, false);
//解封装的视频编码参数,传递给解码上下文
avcodec_parameters_to_context(context, vs->codecpar);
decoder->setContext(context);
decoder->Open();

 

  7.解封装

ret = av_read_frame(ic, &pkt);
if (ret != 0)
{
    qDebug() << "解码结束";
    PrintErr(ret);
    break;
}

 

  8.解码,渲染,并重复解封装、解码、渲染,直到文件读取完成

if (!decoder->Send(&pkt))break;
while (decoder->Receive(frame))
{
    renderView->DrawFrame(frame);
    std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 25));
}
qDebug() << "视频,width=" << vs->codecpar->width << ",height=" << vs->codecpar->height;

 

  9.最后别忘记销毁操作

if (isRunning)
{
    isRunning = false;
    if (renderThread && renderThread->joinable())
    {
        renderThread->join();
    }
    if (renderView)
    {
        renderView->Close();
        renderView = nullptr;
    }
}

 

posted on 2025-05-27 16:37  飘杨......  阅读(51)  评论(0)    收藏  举报