用QT6、QML、FFMPEG写一个有快进功能的影音播放程序

2024-06-09 03:20

本文主要是介绍用QT6、QML、FFMPEG写一个有快进功能的影音播放程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

程序如图:

开发环境在ubuntu下,如果改windows下,也就改一下cmakelists.txt。windows下如何配置ffmpeg以前的文章有写,不再重复。

源程序如下:

GitHub - wangz1155/ffmpegAudioThread: 用qt6,qml,ffmpeg,写一个有快进功能的影音播放GitHub - wangz1155/ffmpegAudioThread: 用qt6,qml,ffmpeg,

程序看不懂,可以拷贝出来让AI帮忙分析,不一定要用chatGPT、copilot,国内的“通义”、“天工”、"豆包"、”kimi“等等也很多。

主要文件:

CMakeLists.txt

Main.qml

main.cpp

videoplayer.h

videoplayer.cpp

可以学到的主要知识:

1、cmake配置

2、qt、qml、c++联合编程,指针有点多,要特别注意,这个程序经过测试基本能用,但有些实验代码还在,未充分整理。

3、ffmpeg、滤镜

一、CMakeLists.txt文件主要内容

cmake_minimum_required(VERSION 3.16)

project(ffmpegAudioThread VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)


find_package(Qt6 6.4 REQUIRED COMPONENTS Quick Multimedia)
find_package(FFmpeg REQUIRED)
include_directories(${FFMPEG_INCLUDE_DIRS})

set(FFMPEG_LIBRARIES /usr/lib/x86_64-linux-gnu)

qt_standard_project_setup()

qt_add_executable(appffmpegAudioThread
    main.cpp
)

qt_add_qml_module(appffmpegAudioThread
    URI ffmpegAudioThread
    VERSION 1.0
    QML_FILES
        Main.qml
        SOURCES videoplayer.h videoplayer.cpp
)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appffmpegAudioThread PROPERTIES
#    MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appffmpeg01
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_link_libraries(appffmpegAudioThread
    PRIVATE Qt6::Quick Qt6::Multimedia
    ${FFMPEG_LIBRARIES}/libavformat.so
    ${FFMPEG_LIBRARIES}/libavcodec.so
    ${FFMPEG_LIBRARIES}/libavutil.so
    ${FFMPEG_LIBRARIES}/libswscale.so
    ${FFMPEG_LIBRARIES}/libswresample.so
    ${FFMPEG_LIBRARIES}/libavfilter.so
)

include(GNUInstallDirs)
install(TARGETS appffmpegAudioThread
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

二、main.qml主要内容

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Dialogs
import ffmpegAudioThread

Window {
    id:window001
    visible: true
    width: 900
    height: 600
    title: "Video Player"
    color:"black"

    function formatTime(milliseconds) {
        var seconds = Math.floor(milliseconds / 1000);
        var minutes = Math.floor(seconds / 60);
        var hours = Math.floor(minutes / 60);
        seconds = seconds % 60;
        minutes = minutes % 60;
        // 使用padStart来确保数字总是显示两位
        var formattedTime = hours.toString().padStart(2, '0') + ":" +
                            minutes.toString().padStart(2, '0') + ":" +
                            seconds.toString().padStart(2, '0');
        return formattedTime;
    }

    FileDialog{
        id:fileDialog
        onAccepted: {
            console.log(fileDialog.selectedFile)
            if (videoPlayer.loadFile(fileDialog.selectedFile)) {
                videoPlayer.play();
            }
        }
    }

    Row {
        anchors.fill: parent
        spacing: 10

            VideoPlayer {
                id: videoPlayer
                width: parent.width * 0.90
                height: parent.height

                onVideoWidthChanged: {
                    window001.width=videoPlayer.videoWidth

                }
                onVideoHeightChanged: {
                    window001.height=videoPlayer.videoHeight

                }
                onDurationChanged: {
                    slider.to=videoPlayer.duration

                }
                onPositionChanged: {

                    if(!slider.pressed){

                        slider.value=videoPlayer.position

                    }
                }

                Slider{
                    id:slider
                    width:videoPlayer.width
                    height:20
                    anchors.bottom:parent.bottom
                    from: 0
                    to:videoPlayer.duration
                    value:videoPlayer.position
                    visible:true
                    opacity: 0
                    onValueChanged: {
                        if(slider.pressed){

                           var intValue=Math.floor(slider.value)

                           videoPlayer.setPosi(intValue)
                        }
                    }
                    Keys.onPressed: {
                        if(event.key===Qt.Key_Escape){
                            window001.visibility=Window.Windowed
                            window001.width=videoPlayer.videoWidth
                            window001.height=videoPlayer.videoHeight
                            videoPlayer.width=window001.width * 0.90
                            videoPlayer.height=window001.height

                       }
                    }
                    MouseArea{
                        id:mouseArea
                        anchors.fill: parent
                        hoverEnabled: true
                        onEntered: {

                            slider.opacity=1
                        }
                        onExited:{

                            slider.opacity=0
                        }
                        onClicked: {
                            var newPosition=mouse.x/width;
                            slider.value=slider.from+newPosition*(slider.to-slider.from);
                            var intValue=Math.floor(slider.value)
                            videoPlayer.setPosi(intValue)

                        }
                    }
                    Label{
                        id:valueLabel
                        text:formatTime(slider.value)
                        color: "white"
                        x:slider.leftPadding+slider.visualPosition*(slider.width-width)
                        y:slider.topPadding-height
                    }
                }
            }

        Column {
            id: column
            width: parent.width * 0.1
            height: parent.height
            spacing: 10
            anchors.verticalCenter: parent.verticalCenter

            Button {
                text: "开始"
                onClicked: {
                    fileDialog.open()
                }
            }
            Button {
                text: "暂停"
                onClicked: videoPlayer.pause()
            }
            Slider{
                id:playbackSpeedSlider
                from:0.5
                to:2.0
                value:1.0
                stepSize: 0.1
                orientation: Qt.Vertical
                onValueChanged: {
                    playbackSpeedLabel.text="速度:"+playbackSpeedSlider.value.toFixed(1)
                    videoPlayer.audioSpeed(playbackSpeedSlider.value.toFixed(1))
                }
            }
            Label{
                id:playbackSpeedLabel
                color:"white"
                text: "速度:1.0"
            }
            Button{
                text:"全屏"
                onClicked: {
                    if(window001.visibility===Window.FullScreen){
                        window001.visibility=Window.Windowed
                        window001.width=videoPlayer.videoWidth
                        window001.height=videoPlayer.videoHeight
                        videoPlayer.width=window001.width * 0.90
                        videoPlayer.height=window001.height

                    }else{
                        window001.visibility=Window.FullScreen

                        var v_width=videoPlayer.videoWidth
                        var v_height=videoPlayer.videoHeight

                        videoPlayer.width=1920

                        videoPlayer.height=1920*(v_height/v_width)

                    }
                }
            }
            Keys.onPressed: {
                if(event.key===Qt.Key_Escape){
                    window001.visibility=Window.Windowed
                    window001.width=videoPlayer.videoWidth
                    window001.height=videoPlayer.videoHeight
                    videoPlayer.width=window001.width * 0.90
                    videoPlayer.height=window001.height
                }
            }
        }
    }
}

三、main.cpp主要内容

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/ffmpegAudioThread/Main.qml"));
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreationFailed,
        &app,
        []() { QCoreApplication::exit(-1); },
        Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

四、videoplayer.h、videoplayer.cpp主要内容,这部分是核心。

定义了两个类,class AudioThread : public QThread,继承之QThread,可以线程运行用于播放声音。class VideoPlayer : public QQuickPaintedItem,继承之QQuickPaintedItem,主要用于在qml中绘制QImage,来实现视频播放。

(一)videoplayer.h

#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H

#include <QObject>
#include <QQuickPaintedItem>
#include <QImage>
#include <QTimer>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>
#include <QPainter>
#include <QtMultimedia>
#include <QWaitCondition>
#include <QThread>
#include <QString>
#include <chrono>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
}
struct AudioData{
    QByteArray buffer;
    qint64 pts;
    qint64 duration;
};

class AudioThread : public QThread
{
    Q_OBJECT
    QML_ELEMENT
public:
    AudioThread(QObject *parent = nullptr);
    ~AudioThread();

    void run() override;

    void cleanQueue();

    void setCustomTimebase(qint64 *timebase);

    qint64 audioTimeLine=0;

    void conditionWakeAll();

    void pause();
    void resume();
    int init_filters(const char *filters_descr);


    void stop();

    void deleteAudioSink();


    void initAudioThread();
    QAudioFormat::SampleFormat ffmpegToQtSampleFormat(AVSampleFormat ffmpegFormat);
signals:
    void audioFrameReady(qint64 pts);
    void audioProcessed();
    void sendAudioTimeLine(qint64 timeLine);
private slots:
    void processAudio();
public slots:
    void handleAudioPacket(AVPacket *packet);
    void receiveAudioParameter(AVFormatContext *format_Ctx,AVCodecContext *audioCodec_Ctx,int *audioStream_Index);
    void setPlaybackSpeed(double speed);

private:

    AVFormatContext *formatCtx = nullptr;
    int *audioStreamIndex = nullptr;
    // 音频编解码器上下文
    AVCodecContext *audioCodecCtx;
    // 音频重采样上下文
    SwrContext *swrCtx;
    // 音频输出设备
    QAudioSink *audioSink;
    // 音频设备输入/输出接口
    QIODevice *audioIODevice;
    QMutex mutex;
    QWaitCondition condition;
    bool shouldStop = false;

    qint64 audioClock = 0; /**< 音频时钟 */
    qint64 *audioTimebase=nullptr;
    bool pauseFlag=false;
    QQueue<AudioData> audioData;
    QQueue<AVPacket*> packetQueue;

    AVFilterContext *buffersink_ctx=nullptr;
    AVFilterContext *buffersrc_ctx=nullptr;
    AVFilterGraph *filter_graph=nullptr;
    qint64 originalPts=0;

    double playbackSpeed=2.0;
    char filters_descr[64]={0};
    int data_size=0;

    QMediaDevices *outputDevices=nullptr;
    QAudioDevice outputDevice;
    QAudioFormat format;

    QTimer *timer;
    bool timerFlag=false;

};


class VideoPlayer : public QQuickPaintedItem
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(int videoWidth READ videoWidth  NOTIFY videoWidthChanged)
    Q_PROPERTY(int videoHeight READ videoHeight NOTIFY videoHeightChanged)
    Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged)
    Q_PROPERTY(qint64 position READ position WRITE setPosition NOTIFY positionChanged)

public:
    VideoPlayer(QQuickItem *parent = nullptr);
    ~VideoPlayer();
    Q_INVOKABLE bool loadFile(const QString &fileName);
    Q_INVOKABLE void play();
    Q_INVOKABLE void pause();
    Q_INVOKABLE void stop();
    Q_INVOKABLE void setPosi(qint64 position);
    Q_INVOKABLE void audioSpeed(qreal speed);

    int videoWidth() const {
        return m_videoWidth;
    }
    int videoHeight() const{
        return m_videoHeight;
    }
    qint64 duration() const{
        return m_duration;
    }
    qint64 position() const{
        return m_position;
    }
    void setPosition(int p);

    void cleanVideoPacketQueue();

    qint64 turnPoint=0;

    void delay(int milliseconds);
signals:
    void videoWidthChanged();
    void videoHeightChanged();
    void durationChanged(qint64 duration);
    void positionChanged(qint64 position);
    void deliverPacketToAudio(AVPacket *deliverPacket);
    void sendAudioParameter(AVFormatContext *formatCtx,AVCodecContext *audioCodecCtx,int *audioStreamIndex);
    void sendSpeed(double speed);

protected:
    void paint(QPainter *painter) override;
public slots:
    void receiveAudioTimeLine(qint64 timeLine);

private slots:
    void onTimeout();
private:
    void cleanup();
    void decodeVideo();

    AVFormatContext *formatCtx = nullptr;
    AVCodecContext *videoCodecCtx = nullptr;
    SwsContext *swsCtx = nullptr;
    AVCodecContext *audioCodecCtx=nullptr;
    SwrContext *swrCtx=nullptr;


    QImage currentImage;
    QTimer *timer = nullptr;
    QTimer *syncTimer=nullptr;
    int videoStreamIndex = -1;
    int audioStreamIndex = -1;
    AudioThread *audioThread = nullptr;
    AVPacket *audioPacket=nullptr;
    qint64 audioClock = 0; /**< 音频时钟 */
    qint64 videoClock = 0; /**< 视频时钟 */
    QMutex mutex;
    double audioPts=0;
    QQueue<AVFrame*> videoQueue;
    QQueue<AVPacket*> videoPacketQueue;


    int m_videoWidth=0;
    int m_videoHeight=0;
    qint64 m_duration=0;
    qint64 m_position=0;

    qint64 customTimebase=0;

};


#endif // VIDEOPLAYER_H

(二)videoplayer.cpp

#include "videoplayer.h"
#include <QDebug>


AudioThread::AudioThread(QObject *parent)
    : QThread(parent),
    audioCodecCtx(nullptr),
    swrCtx(nullptr),
    audioSink(nullptr),
    audioIODevice(nullptr),
    buffersink_ctx(nullptr),
    buffersrc_ctx(nullptr),
    filter_graph(nullptr),
    shouldStop(false),
    pauseFlag(false),
    playbackSpeed(1.0),
    data_size(0){

}

AudioThread::~AudioThread() {
    shouldStop=true;
    condition.wakeAll();
    wait();
}

//设置播放速度
void AudioThread::setPlaybackSpeed(double speed)
{
    playbackSpeed=speed;
    qDebug()<<"playbackSpeed"<<playbackSpeed;

    QMutexLocker locker(&mutex);

    if(filter_graph!=nullptr){

        qWarning() << "无法初始化";
        avfilter_graph_free(&filter_graph);
        timerFlag=true;
        snprintf(filters_descr, sizeof(filters_descr), "atempo=%.1f", playbackSpeed);

        if (init_filters(filters_descr) < 0) {
            qWarning() << "无法初始化滤镜图表";
            return;
        }
    }

}

void AudioThread::pause() {
    QMutexLocker locker(&mutex);
    pauseFlag=true;
}
void AudioThread::resume() {
    QMutexLocker locker(&mutex);
    if(pauseFlag){
        pauseFlag=false;
    }
    condition.wakeAll();
}
void AudioThread::stop() {
    QMutexLocker locker(&mutex);
    shouldStop = true;
    condition.wakeAll();
}

//暂停和清除音频队列,待完善
void AudioThread::deleteAudioSink()
{

    pause();

    cleanQueue();

}

//接收音频放入队列
void AudioThread::handleAudioPacket(AVPacket *packet) {
    QMutexLocker locker(&mutex);
    packetQueue.enqueue(packet);
    condition.wakeOne();
}

//接收主进程传递的参数
void AudioThread::receiveAudioParameter(AVFormatContext *format_Ctx, AVCodecContext *audioCodec_Ctx, int *audioStream_Index)
{
    formatCtx=format_Ctx;
    audioCodecCtx=audioCodec_Ctx;
    audioStreamIndex=audioStream_Index;
}

void AudioThread::conditionWakeAll(){
    condition.wakeAll();
}

//清除音频队列
void AudioThread::cleanQueue(){
    QMutexLocker locker(&mutex);
    while(!packetQueue.isEmpty()){
        AVPacket *packet=packetQueue.dequeue();
        av_packet_unref(packet);
        av_packet_free(&packet);
    }
    while(!audioData.isEmpty()){
        audioData.dequeue();
    }

    locker.unlock();
}

//滤镜初始化
int AudioThread::init_filters(const char *filters_descr) {
    char args[512];
    int ret = 0;
    const AVFilter *buffersrc  = avfilter_get_by_name("abuffer");
    const AVFilter *buffersink = avfilter_get_by_name("abuffersink");
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs  = avfilter_inout_alloc();

    filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    if (!audioCodecCtx->channel_layout)
        audioCodecCtx->channel_layout =
            av_get_default_channel_layout(audioCodecCtx->channels);
    snprintf(args, sizeof(args),
             "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64,
             audioCodecCtx->time_base.num, audioCodecCtx->time_base.den, audioCodecCtx->sample_rate,
             av_get_sample_fmt_name(audioCodecCtx->sample_fmt), audioCodecCtx->channel_layout);
    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, nullptr, filter_graph);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer source\n");
        goto end;
    }

    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       nullptr, nullptr, filter_graph);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer sink\n");
        goto end;
    }


    outputs->name       = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = nullptr;

    inputs->name       = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx    = 0;
    inputs->next       = nullptr;

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                        &inputs, &outputs, nullptr)) < 0)
        goto end;
    if ((ret = avfilter_graph_config(filter_graph, nullptr)) < 0)
        goto end;

end:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    if(ret<0&& filter_graph){
        avfilter_graph_free(&filter_graph);
        filter_graph=nullptr;

    }

    return ret;
}

//音频播放
void AudioThread::processAudio()
{
    AudioData audioDataTemp;
    if (shouldStop) {
        quit();
        return;
    }
    //QMutexLocker locker(&mutex);
    if (pauseFlag) {
        return;
    }

    int bytesFree = audioSink->bytesFree();
    if (!audioData.isEmpty() && bytesFree >= audioData.head().buffer.size()) {
        AudioData dataTemp = audioData.dequeue();
        audioTimeLine = dataTemp.pts + dataTemp.duration + audioSink->bufferSize() / data_size * dataTemp.duration;
        emit sendAudioTimeLine(audioTimeLine);
        qDebug() << "audioTimeLine" << audioTimeLine;
        audioIODevice->write(dataTemp.buffer);
    } else {
        qDebug() << "duration_error";
    }

    if (packetQueue.isEmpty()) {

        qDebug() << "packetQueue.isEmpty()" ;
        return;
        //condition.wait(&mutex);
        if (shouldStop) return;
    }
    if (!packetQueue.isEmpty()) {
        AVPacket *packet = packetQueue.dequeue();

        AVFrame *frame = av_frame_alloc();
        if (!frame) {
            qWarning() << "无法分配音频帧";
            return;
        }

        int ret = avcodec_send_packet(audioCodecCtx, packet);
        if (ret < 0) {
            qWarning() << "无法发送音频包到解码器";
            av_packet_unref(packet);
            av_packet_free(&packet);
            av_frame_free(&frame);
            return;
        }

        while (ret >= 0) {
            ret = avcodec_receive_frame(audioCodecCtx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                av_frame_free(&frame);
                break;
            } else if (ret < 0) {
                qWarning() << "无法接收解码后的音频帧";
                av_frame_free(&frame);
                break;
            }

            originalPts = frame->pts * av_q2d(formatCtx->streams[*audioStreamIndex]->time_base) * 1000;

            if (av_buffersrc_add_frame(buffersrc_ctx, frame) < 0) {
                qWarning() << "无法将音频帧送入滤镜链";
                av_frame_free(&frame);
                break;
            }

            while (true) {
                AVFrame *filt_frame = av_frame_alloc();
                if (!filt_frame) {
                    qWarning() << "无法分配滤镜后的音频帧";
                    break;
                }

                ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    av_frame_free(&filt_frame);
                    break;
                } else if (ret < 0) {
                    qWarning() << "无法从滤镜链获取处理后的音频帧";
                    av_frame_free(&filt_frame);
                    break;
                }
                if (!filt_frame || filt_frame->nb_samples <= 0) {
                    qWarning() << "滤镜数据nb_samples<=0";
                    av_frame_free(&filt_frame);
                    continue;
                }
                if (!filt_frame->data[0][0]) {
                    qWarning() << "滤镜数据为空";
                    av_frame_free(&filt_frame);
                    continue;
                }
                if (filt_frame->extended_data == nullptr) {
                    qWarning() << "滤镜数据extended_data";
                    av_frame_free(&filt_frame);
                    continue;
                }

                data_size = av_samples_get_buffer_size(nullptr, filt_frame->channels,
                                                       filt_frame->nb_samples,
                                                       (AVSampleFormat)filt_frame->format, 1);
                if (data_size < 0) {
                    qWarning() << "无法获取缓冲区大小";
                    av_frame_unref(filt_frame);
                    break;
                }


                audioDataTemp.duration = ((filt_frame->nb_samples * 1000) / filt_frame->sample_rate);
                audioDataTemp.pts = originalPts;
                qDebug() << "audioDataTemp.duration" << audioDataTemp.duration;
                qDebug() << "audioDataTemp.pts" << audioDataTemp.pts;

                audioDataTemp.buffer = QByteArray((char*)filt_frame->data[0], data_size);
                audioData.enqueue(audioDataTemp);
                qDebug() << "audioData.size()" << audioData.size();

                av_frame_free(&filt_frame);
            }

            av_frame_free(&frame);
        }

        av_packet_unref(packet);
        av_packet_free(&packet);
    }

    emit audioProcessed();

}

//返回音频类型
QAudioFormat::SampleFormat AudioThread::ffmpegToQtSampleFormat(AVSampleFormat ffmpegFormat) {
    switch (ffmpegFormat) {
    case AV_SAMPLE_FMT_U8:   return QAudioFormat::UInt8;
    case AV_SAMPLE_FMT_S16:  return QAudioFormat::Int16;
    case AV_SAMPLE_FMT_S32:  return QAudioFormat::Int32;
    case AV_SAMPLE_FMT_FLT:  return QAudioFormat::Float;
    case AV_SAMPLE_FMT_DBL:  // Qt没有直接对应的64位浮点格式
    case AV_SAMPLE_FMT_U8P:  // 平面格式
    case AV_SAMPLE_FMT_S16P: // 平面格式
    case AV_SAMPLE_FMT_S32P: // 平面格式
    case AV_SAMPLE_FMT_FLTP: // 平面格式
    case AV_SAMPLE_FMT_DBLP: // 平面格式unknown
    default: return QAudioFormat::Float;
    }
}

//初始化音频
void AudioThread::initAudioThread(){

    if(filter_graph!=nullptr){
        avfilter_graph_free(&filter_graph);
    }

    timerFlag=true;
    snprintf(filters_descr, sizeof(filters_descr), "atempo=%.1f", playbackSpeed);


    outputDevices=new QMediaDevices();
    outputDevice=outputDevices->defaultAudioOutput();
    //format=outputDevice.preferredFormat();

    format.setSampleRate(audioCodecCtx->sample_rate);
    format.setChannelCount(audioCodecCtx->channels);
    //format.setSampleFormat(QAudioFormat::Float);
    format.setSampleFormat(ffmpegToQtSampleFormat(audioCodecCtx->sample_fmt));


    audioSink = new QAudioSink(outputDevice, format);
    audioIODevice =audioSink->start();


    if (init_filters(filters_descr) < 0) {
        qWarning() << "无法初始化滤镜图表";
        return;
    }

}

//初始化音频,开始timer
void AudioThread::run() {

    timerFlag=true;
    snprintf(filters_descr, sizeof(filters_descr), "atempo=%.1f", playbackSpeed);


    outputDevices=new QMediaDevices();
    outputDevice=outputDevices->defaultAudioOutput();
    //format=outputDevice.preferredFormat();

    format.setSampleRate(audioCodecCtx->sample_rate);
    format.setChannelCount(audioCodecCtx->channels);
    //format.setSampleFormat(QAudioFormat::Float);
    format.setSampleFormat(ffmpegToQtSampleFormat(audioCodecCtx->sample_fmt));


    audioSink = new QAudioSink(outputDevice, format);
    audioIODevice =audioSink->start();


    if (init_filters(filters_descr) < 0) {
        qWarning() << "无法初始化滤镜图表";
        return;
    }

    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &AudioThread::processAudio);
    timer->start(10); // 每10ms触发一次
    exec();
    avfilter_graph_free(&filter_graph);

}

VideoPlayer::VideoPlayer(QQuickItem *parent)
    : QQuickPaintedItem(parent),
    formatCtx(nullptr),
    videoCodecCtx(nullptr),
    swsCtx(nullptr),
    audioCodecCtx(nullptr),
    swrCtx(nullptr),
    timer(new QTimer(this)),
    customTimebase(0),
    audioThread(new AudioThread(this)) {
    connect(timer, &QTimer::timeout, this, &VideoPlayer::onTimeout);
    connect(this,&VideoPlayer::deliverPacketToAudio,audioThread,&AudioThread::handleAudioPacket);
    connect(audioThread,&AudioThread::sendAudioTimeLine,this,&VideoPlayer::receiveAudioTimeLine);
    connect(this,&VideoPlayer::sendAudioParameter,audioThread,&AudioThread::receiveAudioParameter);
    connect(this,&VideoPlayer::sendSpeed,audioThread,&AudioThread::setPlaybackSpeed);
    avformat_network_init();
    av_register_all(); // 注册所有编解码器
    avfilter_register_all();
}

VideoPlayer::~VideoPlayer() {
    stop();
    audioThread->quit();
    audioThread->wait();
    delete audioThread;

}


//打开视频文件,如果打开成功,qml中执行 play();文件选择用的 qml
bool VideoPlayer::loadFile(const QString &fileName) {
    stop();
    formatCtx = avformat_alloc_context();
    if (avformat_open_input(&formatCtx, fileName.toStdString().c_str(), nullptr, nullptr) != 0) {
        qWarning() << "无法打开文件";
        return false;
    }

    if (avformat_find_stream_info(formatCtx, nullptr) < 0) {
        qWarning() << "无法获取流信息";
        return false;
    }

    for (unsigned int i = 0; i < formatCtx->nb_streams; ++i) {
        if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
        } else if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStreamIndex = i;
        }
    }

    if (videoStreamIndex == -1) {
        qWarning() << "未找到视频流";
        return false;
    }

    if (audioStreamIndex == -1) {
        qWarning() << "未找到音频流";
        return false;
    }

    AVCodec *videoCodec = avcodec_find_decoder(formatCtx->streams[videoStreamIndex]->codecpar->codec_id);
    if (!videoCodec) {
        qWarning() << "未找到视频解码器";
        return false;
    }

    videoCodecCtx = avcodec_alloc_context3(videoCodec);
    avcodec_parameters_to_context(videoCodecCtx, formatCtx->streams[videoStreamIndex]->codecpar);
    if (avcodec_open2(videoCodecCtx, videoCodec, nullptr) < 0) {
        qWarning() << "无法打开视频解码器";
        return false;
    }

    swsCtx = sws_getContext(videoCodecCtx->width, videoCodecCtx->height, videoCodecCtx->pix_fmt,
                            videoCodecCtx->width, videoCodecCtx->height, AV_PIX_FMT_RGB24,
                            SWS_BILINEAR, nullptr, nullptr, nullptr);

    AVCodec *audioCodec = avcodec_find_decoder(formatCtx->streams[audioStreamIndex]->codecpar->codec_id);
    if (!audioCodec) {
        qWarning() << "未找到音频解码器";
        return false;
    }

    audioCodecCtx = avcodec_alloc_context3(audioCodec);
    if(avcodec_parameters_to_context(audioCodecCtx, formatCtx->streams[audioStreamIndex]->codecpar)<0){
        qWarning() << "无法复制音频解码器上下文";
        return false;
    }

    if (avcodec_open2(audioCodecCtx, audioCodec, nullptr) < 0)
    {
        qWarning() << "无法打开音频解码器";
        return false;
    }

    emit sendAudioParameter(formatCtx,audioCodecCtx,&audioStreamIndex);

    if(!audioThread->isRunning()){
        audioThread->start();
    }else{
        audioThread->initAudioThread();
    }
    audioThread->resume();

    m_duration=formatCtx->duration / AV_TIME_BASE *1000;

    emit durationChanged(m_duration);

    customTimebase=0;

    return true;
}

void VideoPlayer::play() {
    if (!timer->isActive()) {
       timer->start(1000 / 150);//用150是保证2倍数时,数据量足够,避免出现卡顿。
    }
}

void VideoPlayer::pause() {

    if (timer->isActive()) {
        timer->stop();
        audioThread->pause();
    }else{
        timer->start();
        audioThread->resume();
    }

}

void VideoPlayer::stop() {
    if (timer->isActive()) {
        timer->stop();
    }
    cleanup();
}

//绘制视频
void VideoPlayer::paint(QPainter *painter) {
    if (!currentImage.isNull()) {
        painter->drawImage(boundingRect(), currentImage);
    }
}

//接收音频时间线,调整视频时间线。
void VideoPlayer::receiveAudioTimeLine(qint64 timeLine)
{
    customTimebase=timeLine+15;
}

//视频队列清空
void VideoPlayer::cleanVideoPacketQueue(){
    //QMutexLocker locker(&mutex);
    while(!videoPacketQueue.isEmpty()){
        //packetQueue.dequeue();
        AVPacket *packet=videoPacketQueue.dequeue();
        av_packet_unref(packet);
        av_packet_free(&packet);
    }
    //locker.unlock();
}

//定义了Q_PROPERTY(qint64 position READ position WRITE setPosition NOTIFY positionChanged) 必须要有
void VideoPlayer::setPosition(int p){
    /*
    qint64 position=p;
    QMutexLocker locker(&mutex);
    if(av_seek_frame(formatCtx,-1,position*AV_TIME_BASE/1000,AVSEEK_FLAG_ANY)<0){
        qWarning()<<"无法跳转到指定位置";
        return;
    }
    avcodec_flush_buffers(videoCodecCtx);
    avcodec_flush_buffers(audioCodecCtx);
    m_position=position;
    emit positionChanged(m_position);*/
}

//查找定位,用于进度条拖拽。
void VideoPlayer::setPosi(qint64 position){


    if (timer->isActive()) {
        timer->stop();
    }else{

    }
    audioThread->deleteAudioSink();

    qint64 target_ts=position*1000;

    avcodec_flush_buffers(videoCodecCtx);
    avcodec_flush_buffers(audioCodecCtx);

    cleanVideoPacketQueue();

    //if(av_seek_frame(formatCtx,-1,target_ts,AVSEEK_FLAG_BACKWARD)<0){
    if(avformat_seek_file(formatCtx,-1,INT64_MIN,target_ts,INT64_MAX,AVSEEK_FLAG_BACKWARD)){   //这方法查找更准确
        qWarning()<<"无法跳转到指定位置";
        return;
    }

    if (timer->isActive()) {

    }else{
        timer->start();
    }

    audioThread->resume();

    m_position=position;
    customTimebase=position;
    //turnPoint=position;
    emit positionChanged(m_position);

}

//发送速度参数给音频滤镜
void VideoPlayer::audioSpeed(qreal speed)
{
    double s=speed;
    emit sendSpeed(s);

}

//主线程延迟标准程序
void VideoPlayer::delay(int milliseconds) {
    QTime dieTime = QTime::currentTime().addMSecs(milliseconds);
    while (QTime::currentTime() < dieTime) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
}

//定时器,定时执行内容
void VideoPlayer::onTimeout() {
    AVPacket *packet=av_packet_alloc();
    if(!packet) return;

    if (av_read_frame(formatCtx, packet) >= 0) {
        if (packet->stream_index == videoStreamIndex) {
            videoPacketQueue.enqueue(packet);

            decodeVideo();
        } else if (packet->stream_index == audioStreamIndex) {
            AVPacket *audioPacket=av_packet_alloc();
            if(!audioPacket){
                av_packet_unref(packet);
                av_packet_free(&packet);
                return;
            }
            av_packet_ref(audioPacket,packet);

            emit deliverPacketToAudio(audioPacket);
        }
    }else{
        decodeVideo();
    }
}


//解码视频,并刷新
void VideoPlayer::decodeVideo() {

    if(videoPacketQueue.isEmpty()){
        return;
    }

    AVPacket *packet=videoPacketQueue.first();
    qint64 videoPts=packet->pts*av_q2d(formatCtx->streams[videoStreamIndex]->time_base)*1000;//转换为毫秒

    if(videoPts>(customTimebase)){
        return;
    }else{
        packet=videoPacketQueue.dequeue();
    }

    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        qWarning() << "无法分配视频帧";
        av_packet_unref(packet);
        av_packet_free(&packet);
        return;
    }

    int ret = avcodec_send_packet(videoCodecCtx, packet);
    if (ret < 0) {
        qWarning() << "无法发送视频包到解码器";
        av_frame_free(&frame);
        av_packet_unref(packet);
        av_packet_free(&packet);
        return;
    }

    ret = avcodec_receive_frame(videoCodecCtx, frame);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        av_frame_free(&frame);
        av_packet_unref(packet);
        av_packet_free(&packet);
        return;
    } else if (ret < 0) {
        qWarning() << "无法接收解码后的视频帧";
        av_frame_free(&frame);
        av_packet_unref(packet);
        av_packet_free(&packet);
        return;
    }

    av_packet_unref(packet);
    av_packet_free(&packet);


    m_position=customTimebase;            //以音频轴更新视频轴
    emit positionChanged(m_position);


    if(m_videoWidth!=frame->width||m_videoHeight!=frame->height){
        m_videoWidth=frame->width;
        m_videoHeight=frame->height;
        emit videoWidthChanged();
        emit videoHeightChanged();
    }
    // 缩放视频帧
    AVFrame *rgbFrame = av_frame_alloc();
    if (!rgbFrame) {
        qWarning() << "无法分配RGB视频帧";
        av_frame_free(&frame);
        return;
    }
    rgbFrame->format = AV_PIX_FMT_RGB24;
    rgbFrame->width = videoCodecCtx->width;
    rgbFrame->height = videoCodecCtx->height;
    ret = av_frame_get_buffer(rgbFrame, 0);
    if (ret < 0) {
        qWarning() << "无法分配RGB视频帧数据缓冲区";
        av_frame_free(&frame);
        av_frame_free(&rgbFrame);
        return;
    }
    sws_scale(swsCtx, frame->data, frame->linesize, 0, videoCodecCtx->height,
              rgbFrame->data, rgbFrame->linesize);

    // 将RGB视频帧转换为QImage
    currentImage = QImage(rgbFrame->data[0], rgbFrame->width, rgbFrame->height, rgbFrame->linesize[0], QImage::Format_RGB888).copy();


    // 释放视频帧
    av_frame_free(&frame);
    av_frame_free(&rgbFrame);

    update();
}

//清除,用于开始下一个新文件
void VideoPlayer::cleanup() {
    if (swsCtx) {
        sws_freeContext(swsCtx);
        swsCtx = nullptr;
    }
    if (videoCodecCtx) {
        avcodec_free_context(&videoCodecCtx);
        videoCodecCtx = nullptr;
    }
    if (swrCtx) {
        swr_free(&swrCtx);
        swrCtx = nullptr;
    }
    if (audioCodecCtx) {
        avcodec_free_context(&audioCodecCtx);
        audioCodecCtx = nullptr;
    }


    if (formatCtx) {
        avformat_close_input(&formatCtx);
        formatCtx = nullptr;
    }


    while(!videoQueue.isEmpty()){
        AVFrame *frame;
        frame=videoQueue.dequeue();
        av_frame_free(&frame);
    }

    audioThread->deleteAudioSink();
    cleanVideoPacketQueue();

    m_position=0;
    m_duration=0;
    emit positionChanged(m_position);
    emit durationChanged(m_duration);

}

这篇关于用QT6、QML、FFMPEG写一个有快进功能的影音播放程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1044103

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [