使 Qt Player 编解码器独立

Posted

技术标签:

【中文标题】使 Qt Player 编解码器独立【英文标题】:Make Qt Player codec independent 【发布时间】:2016-03-16 05:40:15 【问题描述】:

我开发的 Qt 应用程序可以使用下面的代码播放多个视频文件。

QMediaPlayer *player;
QString fileName = "C:/username/test.h264";
player->setmedia(QUrl::fromLocalFile(fileName));

在启动时我无法播放所有类型的视频文件,所以我在我的系统上安装了编解码器,现在当我的播放器启动编解码器解码器启动时,我的 CPU 使用率达到了很高的水平。(显示波纹管图像)

您可以在上图中看到外部解码器启动的右下角 LAW(红色标签)。

现在,我想让我的 Qt Player 编解码器独立,意味着我知道我的播放器只能播放 .h264 文件,所以我将只使用 h264 解码器,不需要音频,所以我会不使用音频解码器。

据我所知,QMediaPlayer 在出现图片时会启动解码器,如果我错了,请纠正我。那么我该怎么做才能停止外部解码器并在内部解码帧并成功播放?

编辑:使用 FFmpeg 进行音频解码的代码

FFmpegAudio.pro

TARGET = fooAudioFFMPEG
QT       += core gui qml quick widgets
TEMPLATE = app
SOURCES += main.cpp \
    mainwindow.cpp
HEADERS += mainwindow.h \
    wrapper.h
FORMS += mainwindow.ui
QMAKE_CXXFLAGS += -D__STDC_CONSTANT_MACROS

LIBS += -pthread
LIBS += -L/usr/local/lib
LIBS += -lavdevice
LIBS += -lavfilter
LIBS += -lpostproc
LIBS += -lavformat
LIBS += -lavcodec
LIBS += -ldl
LIBS += -lXfixes
LIBS += -lXext
LIBS += -lX11
LIBS += -lasound
LIBS += -lSDL
LIBS += -lx264
LIBS += -lvpx
LIBS += -lvorbisenc
LIBS += -lvorbis
LIBS += -logg
LIBS += -lopencore-amrwb
LIBS += -lopencore-amrnb
LIBS += -lmp3lame
LIBS += -lfaac
LIBS += -lz
LIBS += -lrt
LIBS += -lswscale
LIBS += -lavutil
LIBS += -lm

ma​​inwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui 
    class MainWindow;


class MainWindow : public QMainWindow 
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    void changeEvent(QEvent *e);

private:
    Ui::MainWindow *ui;

private slots:
    void on_pushButton_clicked();
;

#endif // MAINWINDOW_H

wrapper.h

#ifndef WRAPPER_H_
#define WRAPPER_H_

#include <math.h>

#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>

#define INBUF_SIZE 4096
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096



/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt)

    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) 
        if (*p == sample_fmt)
            return 1;
        p++;
    
    return 0;


/* just pick the highest supported samplerate */
static int select_sample_rate(AVCodec *codec)

    const int *p;
    int best_samplerate = 0;

    if (!codec->supported_samplerates)
        return 44100;

    p = codec->supported_samplerates;
    while (*p) 
        best_samplerate = FFMAX(*p, best_samplerate);
        p++;
    
    return best_samplerate;


/* select layout with the highest channel count */
static int select_channel_layout(AVCodec *codec)

    const uint64_t *p;
    uint64_t best_ch_layout = 0;
    int best_nb_channells   = 0;

    if (!codec->channel_layouts)
        return AV_CH_LAYOUT_STEREO;

    p = codec->channel_layouts;
    while (*p) 
        int nb_channels = av_get_channel_layout_nb_channels(*p);

        if (nb_channels > best_nb_channells) 
            best_ch_layout    = *p;
            best_nb_channells = nb_channels;
        
        p++;
    
    return best_ch_layout;


/*
 * Audio encoding example
 */
static void audio_encode_example(const char *filename)

    AVCodec *codec;
    AVCodecContext *c= NULL;
    AVFrame *frame;
    AVPacket pkt;
    int i, j, k, ret, got_output;
    int buffer_size;
    FILE *f;
    uint16_t *samples;
    float t, tincr;

    printf("Encode audio file %s\n", filename);

    /* find the MP2 encoder */
    codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
    if (!codec) 
        fprintf(stderr, "Codec not found\n");
        exit(1);
    

    c = avcodec_alloc_context3(codec);
    if (!c) 
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    

    /* put sample parameters */
    c->bit_rate = 64000;

    /* check that the encoder supports s16 pcm input */
    c->sample_fmt = AV_SAMPLE_FMT_S16;
    if (!check_sample_fmt(codec, c->sample_fmt)) 
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(c->sample_fmt));
        exit(1);
    

    /* select other audio parameters supported by the encoder */
    c->sample_rate    = select_sample_rate(codec);
    c->channel_layout = select_channel_layout(codec);
    c->channels       = av_get_channel_layout_nb_channels(c->channel_layout);

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) 
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    

    f = fopen(filename, "wb");
    if (!f) 
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    

    /* frame containing input raw audio */
    frame = avcodec_alloc_frame();
    if (!frame) 
        fprintf(stderr, "Could not allocate audio frame\n");
        exit(1);
    

    frame->nb_samples     = c->frame_size;
    frame->format         = c->sample_fmt;
    frame->channel_layout = c->channel_layout;

    /* the codec gives us the frame size, in samples,
     * we calculate the size of the samples buffer in bytes */
    buffer_size = av_samples_get_buffer_size(NULL, c->channels, c->frame_size,
                                             c->sample_fmt, 0);
    samples = (uint16_t *)av_malloc(buffer_size);
    if (!samples) 
        fprintf(stderr, "Could not allocate %d bytes for samples buffer\n",
                buffer_size);
        exit(1);
    
    /* setup the data pointers in the AVFrame */
    ret = avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt,
                                   (const uint8_t*)samples, buffer_size, 0);
    if (ret < 0) 
        fprintf(stderr, "Could not setup audio frame\n");
        exit(1);
    

    /* encode a single tone sound */
    t = 0;
    tincr = 2 * M_PI * 440.0 / c->sample_rate;
    for(i=0;i<200;i++) 
        av_init_packet(&pkt);
        pkt.data = NULL; // packet data will be allocated by the encoder
        pkt.size = 0;

        for (j = 0; j < c->frame_size; j++) 
            samples[2*j] = (int)(sin(t) * 10000);

            for (k = 1; k < c->channels; k++)
                samples[2*j + k] = samples[2*j];
            t += tincr;
        
        /* encode the samples */
        ret = avcodec_encode_audio2(c, &pkt, frame, &got_output);
        if (ret < 0) 
            fprintf(stderr, "Error encoding audio frame\n");
            exit(1);
        
        if (got_output) 
            fwrite(pkt.data, 1, pkt.size, f);
            av_free_packet(&pkt);
        
    

    /* get the delayed frames */
    for (got_output = 1; got_output; i++) 
        ret = avcodec_encode_audio2(c, &pkt, NULL, &got_output);
        if (ret < 0) 
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        

        if (got_output) 
            fwrite(pkt.data, 1, pkt.size, f);
            av_free_packet(&pkt);
        
    
    fclose(f);

    av_freep(&samples);
    avcodec_free_frame(&frame);
    avcodec_close(c);
    av_free(c);


/*
 * Audio decoding.
 */
static void audio_decode_example(const char *outfilename, const char *filename)

    AVCodec *codec;
    AVCodecContext *c= NULL;
    int len;
    FILE *f, *outfile;
    uint8_t inbuf[AUDIO_INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
    AVPacket avpkt;
    AVFrame *decoded_frame = NULL;

    av_init_packet(&avpkt);

    printf("Decode audio file %s to %s\n", filename, outfilename);

    /* find the mpeg audio decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
    if (!codec) 
        fprintf(stderr, "Codec not found\n");
        exit(1);
    

    c = avcodec_alloc_context3(codec);
    if (!c) 
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) 
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    

    f = fopen(filename, "rb");
    if (!f) 
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    
    outfile = fopen(outfilename, "wb");
    if (!outfile) 
        av_free(c);
        exit(1);
    

    /* decode until eof */
    avpkt.data = inbuf;
    avpkt.size = fread(inbuf, 1, AUDIO_INBUF_SIZE, f);

    while (avpkt.size > 0) 
        int got_frame = 0;

        if (!decoded_frame) 
            if (!(decoded_frame = avcodec_alloc_frame())) 
                fprintf(stderr, "Could not allocate audio frame\n");
                exit(1);
            
         else
            avcodec_get_frame_defaults(decoded_frame);

        len = avcodec_decode_audio4(c, decoded_frame, &got_frame, &avpkt);
        if (len < 0) 
            fprintf(stderr, "Error while decoding\n");
            exit(1);
        
        if (got_frame) 
            /* if a frame has been decoded, output it */
            int data_size = av_samples_get_buffer_size(NULL, c->channels,
                                                       decoded_frame->nb_samples,
                                                       c->sample_fmt, 1);
            fwrite(decoded_frame->data[0], 1, data_size, outfile);
        
        avpkt.size -= len;
        avpkt.data += len;
        avpkt.dts =
        avpkt.pts = AV_NOPTS_VALUE;
        if (avpkt.size < AUDIO_REFILL_THRESH) 
            /* Refill the input buffer, to avoid trying to decode
             * incomplete frames. Instead of this, one could also use
             * a parser, or use a proper container format through
             * libavformat. */
            memmove(inbuf, avpkt.data, avpkt.size);
            avpkt.data = inbuf;
            len = fread(avpkt.data + avpkt.size, 1,
                        AUDIO_INBUF_SIZE - avpkt.size, f);
            if (len > 0)
                avpkt.size += len;
        
    

    fclose(outfile);
    fclose(f);

    avcodec_close(c);
    av_free(c);
    avcodec_free_frame(&decoded_frame);


/*
 * Main WRAPPER function
 */
void service()


    /* register all the codecs */
    avcodec_register_all();


    audio_encode_example("test.mp2");
    audio_decode_example("test.sw", "test.mp2");



#endif 

ma​​in.cpp

#include <QApplication>
#include "mainwindow.h"

extern "C"
    #include "wrapper.h"


int main(int argc, char *argv[])

    service(); //calling the function service inside the wrapper

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();

ma​​inwindow.cpp

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

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)

    ui->setupUi(this);


MainWindow::~MainWindow()

    delete ui;


void MainWindow::changeEvent(QEvent *e)

    QMainWindow::changeEvent(e);
    switch (e->type()) 
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    


void MainWindow::on_pushButton_clicked()

        this->close();

ma​​inwindow.ui //没什么重要的

谢谢。

【问题讨论】:

【参考方案1】:

Qt 的内置媒体播放器使用系统编解码器。如果你想颠覆它,你将需要使用其他东西。

我没用过,但周围有提到QtAV,它使用的是FFmpeg。

QtAV 网站指出:QtMultimedia 的实现依赖于平台。它在 windows 上使用 dshow,在 linux 上使用 gstream 等。支持新平台可能不是一件容易的工作。

【讨论】:

感谢您的信息,我知道 QtAV 是 Qt 和 FFmpeg 组合的好例子,但它很难理解,我想要一些简单的解决方案来在我的播放器中播放帧。我有一个使用 FFmpeg 包装器解码音频的示例,我将在我的问题区域中添加一些代码部分。【参考方案2】:

仅仅为了在Qt中播放视频文件,你也可以使用libvlc,在著名的媒体播放器(http://www.videolan.org)之后

初始化:

libvlc_instance_t *vlcinstance = libvlc_new(0, NULL);
libvlc_media_player_t *player = libvlc_media_player_new(vlcinstance);
libvlc_media_t *media = libvlc_media_new_path(vlcinstance, file_path);

使用一些 qt 小部件来显示视频:

libvlc_media_player_set_drawable(player, some_widget->winId());

播放视频:

libvlc_media_player_set_media(player, media);
libvlc_media_player_play(player);

这个示例代码当然省略了错误检查。

有关更完整的示例,请参阅以下链接:https://wiki.videolan.org/LibVLC_SampleCode_Qt

【讨论】:

感谢@Philipp Ludwig,我认为这个应用程序也将依赖于平台,如果我没记错的话,意味着最后 vlc 将使用 OS 编解码器。我想要完全独立于平台,这意味着根据上面的示例音频文件使用 FFmpeg 包装器独立编码和解码,不关心哪个操作系统和编码器。 @TejasVirpariya VLC 库附带了它自己的编解码器集合,因此它可以播放各种格式,而无需任何操作系统安装的视频编解码器。 感谢@Philipp Ludwig,我会检查 CPU 性能,如果它可以实现我的目标,我肯定会使用 LibVLC。再次感谢。

以上是关于使 Qt Player 编解码器独立的主要内容,如果未能解决你的问题,请参考以下文章

使用专有编解码器构建 QtWebEngine 5.9.1

QT 5.8 WebEngine Html 5 视频播放器支持

H.264格式,iOS硬编解码 以及 iOS 11对HEVC硬编解码的支持

使用 webengine 视频和音频编解码器

算法 - 使用外部编解码器/调制解调器处理抖动和漂移

mpeg-dash 和编解码器规范