Qt는 FFmpeg를 사용하여 비디오를 재생합니다.

1. 사용 시나리오

  프로젝트는 부팅 비디오를 재생하기 위해 MP4를 로드해야 하고 우리 장치에서 사용하는 아키텍처는 일부 멀티미디어 라이브러리가 부족한 arm 아키텍처이기 때문입니다. 이러한 플러그인 라이브러리를 설치하는 것이 번거로워 결국 FFmpeg를 사용하여 동영상을 재생하기로 결정했습니다.

2. ffmpeg 라이브러리 다운로드 및 컴파일

2.1 소스코드 다운로드

  소스코드 다운로드 경로: https://www.ffmpeg.org/download.html#build-windows

2.2 소스코드 컴파일

  1) 압축 풀기: 소스 코드를 지정된 디렉터리에 넣고 "tar -jxvf ffmpeg-snapshot.tar.bz2"를 실행합니다. xxx.tar.gz 소스파일이라면 "tar -zxvf ffmpeg-xxx.tar.gz"를 사용하세요.

  2) 빌드 디렉터리 "cd ffmpeg", "mkdir build"를 만듭니다.

  3) 컴파일:

  a) 우분투 설명: "./configure --enable-static --prefix=./build"

  b) arm 크로스 컴파일: "./configure --cc=xxx/aarch64-linux-gnu-gcc (QT에 의해 지정된 gcc 경로) --cxx=xxx/aarch64-linux-gnu-g++ --enable-staticc( QT 지정된 g++ 경로) --prefix=./build --enable-cross-compile --arch=arm64 --target-os=linux".

  4) make안装: "make && make install"。

  5) 실행: ffmpeg를 실행해야 하는 경우 --enable-shared 매개변수를 추가하고 환경 변수 "export LD_LIBRARY_PATH=xxx/build/lib/"를 추가해야 합니다.

  6) 도움말 사용: xxx/build/bin 경로에서 "./ffmpeg --help"를 실행합니다.

2.3 일반적인 오류

  1) 크로스 컴파일을 위해서는 해당 gcc 및 g++ 컴파일러와 기타 플랫폼 매개변수를 지정해야 합니다.Linux QTCreator의 경우 프로젝트->빌드 관리->컴파일러->가이드(수동)- 옵션을 통해 해당 컴파일러 경로를 볼 수 있습니다.》 gcc 또는 g++를 두 번 클릭합니다. 작동하기 전에 프로젝트의 현재 실행 및 빌드 플랫폼을 arch64로 선택해야 합니다.

  2) make install 오류: "strip: Unable to recognition the format of the input file": config.mak의 "Trip=strip"을 "Trip=arm -linux-strip"으로 변경합니다.

3. 소스코드 사용

  1. 라이브러리 순서에 주의하면서 헤더 파일과 라이브러리를 프로젝트로 가져옵니다. 예:

INCLUDEPATH += xxx/build/include
LIBS += -Lxxx/xxx -lavformat\
    -lavdevice \
    -lavcodec \
    -lswresample \
    -lavfilter \    
    -lavutil \
    -lswscale

  2.헤더 파일

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
#include <QTimer>
#include <QTime>
#include <QAudioOutput>
extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/version.h>
    #include <libavutil/time.h>
    #include <libavutil/mathematics.h>
    #include <libavfilter/buffersink.h>
    #include <libavfilter/buffersrc.h>
    #include <libavutil/avutil.h>
    #include <libavutil/imgutils.h>
    #include <libavutil/pixfmt.h>
    #include <libswresample/swresample.h>
}

#define MAX_AUDIO_FRAME_SIZE 192000

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
   void timeCallback(void);
   void on_play_clicked();
   void resizeEvent(QResizeEvent* );

private:
    Ui::MainWindow *ui;
    int playVedio(void);
    QTimer *timer;      // 定时播放,根据帧率来
    int vedioW,vedioH;  // 图像宽高
    QList<QPixmap> vedioBuff;   // 图像缓存区

    QString myUrl = QString("E:/workspace/Qt_workspace/ffmpeg/三国之战神无双.mp4");  // 视频地址
    AVFormatContext    *pFormatCtx;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVFrame         *pFrame, *pFrameRGB;
    int ret, got_picture,got_audio;  // 视频解码标志
    int videoindex;        // 视频序号
    // 音频
    int audioindex;        // 音频序号
    AVCodecParameters   *aCodecParameters;
    AVCodec             *aCodec;
    AVCodecContext      *aCodecCtx;
    QByteArray          byteBuf;//音频缓冲
    QAudioOutput        *audioOutput;
    QIODevice           *streamOut;
};

#endif // MAINWINDOW_H

  3. 소스 파일:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
//    qDebug(avcodec_configuration());
//    unsigned version = avcodec_version();
//    QString ch = QString::number(version,10);
//    qDebug()<<"version:"<<version;


    timer = new QTimer(this);
    timer->setTimerType(Qt::PreciseTimer);   // 精准定时设置
    connect(timer,SIGNAL(timeout()),this,SLOT(timeCallback()));
}

void MainWindow::timeCallback(void)
{
    // 视频缓存播放
    if(!vedioBuff.isEmpty())
    {
        ui->label->setPixmap(vedioBuff.at(0));
        vedioBuff.removeAt(0);
    }
    else {
        timer->stop();
    }

    // 音频缓存播放
    if(audioOutput && audioOutput->state() != QAudio::StoppedState && audioOutput->state() != QAudio::SuspendedState)
    {
        int writeBytes = qMin(byteBuf.length(), audioOutput->bytesFree());
        streamOut->write(byteBuf.data(), writeBytes);
        byteBuf = byteBuf.right(byteBuf.length() - writeBytes);
    }
}

void Delay_MSec(unsigned int msec)
{
    QTime _Timer = QTime::currentTime().addMSecs(msec);
    while( QTime::currentTime() < _Timer )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}

int MainWindow::playVedio(void)
{
    QAudioFormat fmt;
    fmt.setSampleRate(44100);
    fmt.setSampleSize(16);
    fmt.setChannelCount(2);
    fmt.setCodec("audio/pcm");
    fmt.setByteOrder(QAudioFormat::LittleEndian);
    fmt.setSampleType(QAudioFormat::SignedInt);
    audioOutput = new QAudioOutput(fmt);
    streamOut = audioOutput->start();

    char *filepath = myUrl.toUtf8().data();
    av_register_all();
    avformat_network_init();
    pFormatCtx = avformat_alloc_context();

    // 打开视频文件,初始化pFormatCtx结构
    if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
        qDebug("视频文件打开失败.\n");
        return -1;
    }
    // 获取音视频流
    if(avformat_find_stream_info(pFormatCtx,NULL)<0){
        qDebug("媒体流获取失败.\n");
        return -1;
    }
    videoindex = -1;
    audioindex = -1;
    //nb_streams视音频流的个数,这里当查找到视频流时就中断了。
    for(int i=0; i<pFormatCtx->nb_streams; i++)
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
    }
    if(videoindex==-1){
        qDebug("找不到视频流.\n");
        return -1;
    }

    //nb_streams视音频流的个数,这里当查找到音频流时就中断了。
    for(int i=0; i<pFormatCtx->nb_streams; i++)
        if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
            audioindex=i;
            break;
    }
    if(audioindex==-1){
        qDebug("找不到音频流.\n");
        return -1;
    }

    //获取视频流编码结构
    pCodecCtx=pFormatCtx->streams[videoindex]->codec;

    float frameNum = pCodecCtx->framerate.num;  // 每秒帧数
    if(frameNum>100)  frameNum = frameNum/1001;
    int frameRate = 1000/frameNum;   //
    qDebug("帧/秒 = %f  播放间隔是时间=%d\n",frameNum,frameRate);

    //查找解码器
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL)
    {
        qDebug("找不到解码器.\n");
        return -1;
    }
    //使用解码器读取pCodecCtx结构
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
    {
        qDebug("打开视频码流失败.\n");
        return -1;
    }

    //获取音频流编码结构-------------------------------------------------------------
    aCodecParameters = pFormatCtx->streams[audioindex]->codecpar;
    aCodec = avcodec_find_decoder(aCodecParameters->codec_id);
    if (aCodec == 0) {
        qDebug("找不到解码器.\n");
        return -1;
    }
    aCodecCtx = avcodec_alloc_context3(aCodec);
    avcodec_parameters_to_context(aCodecCtx, aCodecParameters);
    //使用解码器读取aCodecCtx结构
    if (avcodec_open2(aCodecCtx, aCodec, 0) < 0) {
        qDebug("打开视频码流失败.\n");
        return 0;
    }

    // 清空缓存区
    byteBuf.clear();
    vedioBuff.clear();

    //创建帧结构,此函数仅分配基本结构空间,图像数据空间需通过av_malloc分配
    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    // 获取音频参数
    uint64_t out_channel_layout = aCodecCtx->channel_layout;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = aCodecCtx->sample_rate;
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

    uint8_t *audio_out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE*2);
    SwrContext *swr_ctx = swr_alloc_set_opts(NULL, out_channel_layout, out_sample_fmt,out_sample_rate, aCodecCtx->channel_layout, aCodecCtx->sample_fmt, aCodecCtx->sample_rate, 0, 0);
    swr_init(swr_ctx);

    //创建动态内存,创建存储图像数据的空间
    unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1));
    av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //初始化img_convert_ctx结构
    struct SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    timer->start(frameRate);  //定时间隔播放

    while (av_read_frame(pFormatCtx, packet) >= 0){
        if (packet->stream_index == audioindex){
            int ret = avcodec_decode_audio4(aCodecCtx, pFrame, &got_audio, packet);
            if ( ret < 0)
            {
                qDebug("解码失败.\n");
                return 0;
            }

            if (got_audio)
            {
                int len = swr_convert(swr_ctx, &audio_out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)pFrame->data, pFrame->nb_samples);
                if (len <= 0)
                {
                    continue;
                }
                int dst_bufsize = av_samples_get_buffer_size(0, out_channels, len, out_sample_fmt, 1);
                QByteArray atemp =  QByteArray((const char *)audio_out_buffer, dst_bufsize);
                byteBuf.append(atemp);
            }
        }
        //如果是视频数据
        else if (packet->stream_index == videoindex){
            //解码一帧视频数据
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

            if (ret < 0){
                qDebug("解码失败.\n");
                return 0;
            }
            if (got_picture){
                sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                    pFrameRGB->data, pFrameRGB->linesize);
                QImage img((uchar*)pFrameRGB->data[0],pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                img = img.scaled(vedioW, vedioH);
                QPixmap temp = QPixmap::fromImage(img);
                vedioBuff.append(temp);
                Delay_MSec(frameRate-5);  // 这里需要流出时间来显示,如果不要这个延时界面回卡死到整个视频解码完成才能播放显示
                //ui->label->setPixmap(temp);
            }
        }
        av_free_packet(packet);
    }

    sws_freeContext(img_convert_ctx);
    av_frame_free(&pFrameRGB);
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

void MainWindow::resizeEvent(QResizeEvent* )
{
    vedioW = ui->label->width();
    vedioH = ui->label->height();
}
MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_play_clicked()
{
    vedioW = ui->label->width();
    vedioH = ui->label->height();
    if(timer->isActive())   timer->stop();
    playVedio();
}

추천

출처blog.csdn.net/QtCompany/article/details/129463198