qT 音频播放器

参考QT-ffmpeg+QAudioOutput实现音频播放器_萧海的博客-CSDN博客源代码不全,自己补充完成,顺便吧ffmpeg播放音频复习了一下,下一步有时间增加视频播放部分,由于linux上没有mp3文件,在Windows上测试成功播放视频文件中的音频以及mp3文件界面比较简单,可以自行参考原作者修改。

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMimeData>
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setAcceptDrops(true);
    thread = new playthread();
    connect(thread,SIGNAL(duration(int,int)),this,SLOT(onDuration(int,int)));
    connect(thread,SIGNAL(seekOk()),this,SLOT(onSeekOk()));
//    connect(ui->pushButton_start,SIGNAL(clicked()),this,SLOT(on_btn_start_clicked()));
//    connect(ui->Stopbuttonpause,SIGNAL(clicked()),this,SLOT(on_btn_pause_clicked()));
//    connect(ui->pushButtonstop,SIGNAL(clicked()),this,SLOT(on_btn_stop_clicked()));
//    connect(ui->pushButtonResume,SIGNAL(clicked()),this,SLOT(on_btn_resume_clicked()));
 
 
    sliderSeeking = false;
 
}
 
Widget::~Widget()
{
    delete ui;
    thread->stop();
}
 
void Widget::onSeekOk()
{
    sliderSeeking=false;
}
 
void Widget::onDuration(int currentMs,int destMs)
{
    static int currentMs1=-1,destMs1=-1;
 
    if(currentMs1==currentMs&&destMs1==destMs)
    {
        return;
    }
 
    currentMs1 = currentMs;
    destMs1   =  destMs;
 
    qDebug()<<"onDuration:"<<currentMs<<destMs<<sliderSeeking;
 
    QString currentTime = QString("%1:%2:%3").arg(currentMs1/360000%60,2,10,QChar('0')).arg(currentMs1/6000%60,2,10,QChar('0')).arg(currentMs1/1000%60,2,10,QChar('0'));
 
    QString destTime = QString("%1:%2:%3").arg(destMs1/360000%60,2,10,QChar('0')).arg(destMs1/6000%60,2,10,QChar('0')).arg(destMs1/1000%60,2,10,QChar('0'));
 
 
    ui->label_duration->setText(currentTime+"/"+destTime);
 
    if(!sliderSeeking) //未滑动
    {
        ui->slider->setMaximum(destMs);
        ui->slider->setValue(currentMs);
    }
}
 
void Widget::dragEnterEvent(QDragEnterEvent *event)
{
      if(event->mimeData()->hasUrls())      //判断拖的类型
      {
            event->acceptProposedAction();
      }
      else
      {
            event->ignore();
      }
}
 
void Widget::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasUrls())        //判断放的类型
    {
 
        QList<QUrl> List = event->mimeData()->urls();
 
        if(List.length()!=0)
        {
          ui->line_audioPath->setText(List[0].toLocalFile());
        }
 
    }
    else
    {
          event->ignore();
    }
}
 
 
void Widget::on_btn_start_clicked()
{
 
    sliderSeeking=false;
 
    thread->play(ui->line_audioPath->text());
 
}
 
void Widget::on_btn_stop_clicked()
{
    thread->stop();
}
 
void Widget::on_btn_pause_clicked()
{
    thread->pause();
}
 
void Widget::on_btn_resume_clicked()
{
   thread->resume();
}
 
 
void Widget::on_slider_sliderPressed()
{
    sliderSeeking=true;
}
 
void Widget::on_slider_sliderReleased()
{
 
    thread->seek(ui->slider->value());
 
}
#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QDragEnterEvent>
#include "playthread.h"
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
 
private:
    Ui::Widget *ui;
 
    playthread *thread;
    bool sliderSeeking;
 
public slots:
    void onDuration(int currentMs,int destMs);
    void on_btn_stop_clicked();
    void on_btn_start_clicked();
    void on_btn_pause_clicked();
    void on_btn_resume_clicked();
    void on_slider_sliderPressed();
    void on_slider_sliderReleased();
    void onSeekOk();
 
protected:
    virtual void dragEnterEvent(QDragEnterEvent *event);
    virtual void dropEvent(QDropEvent *event);
};
 
#endif // WIDGET_H
 

#ifndef PLAYTHREAD_H
#define PLAYTHREAD_H
#include <QThread>
#include <QObject>
#include <QAudioOutput>
 
enum controlType
{
    control_none,
    control_stop,
    control_pause,
    control_resume,
    control_play,
    control_type,
    control_seek
};
 
 
class playthread : public QThread
{
    Q_OBJECT;
public:
    explicit playthread(QObject* parent = NULL);
    bool initAudio(int SampleRate);
    void play(QString filePath);
    void stop();
    void pause();
    void resume();
    void seek(int value);
    void debugErr(QString prefix, int err);
    bool runIsBreak();
    void runPlay();
    void run();
 
 
private:
    QAudioOutput *audio;
    controlType type;
    QString filePath;
    int seekMs;
 
signals:
    void ERROR(QString qstrError);
    void seekOk();
    void duration(int ndestMs, int ndestMs1);
 
 
};
 
#endif // PLAYTHREAD_H
#include "playthread.h"
#include <QAudioFormat>
#include <QAudioDeviceInfo>
#include <QDebug>
extern "C"
{
#include <libavutil/error.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
}
 
 
 
playthread::playthread(QObject* parent)
{
    audio = NULL;
    type = control_none;
}
 
bool playthread::initAudio(int SampleRate)
{
    QAudioFormat format;
    if(audio != NULL) {
        return true;
    }
 
    format.setSampleRate(SampleRate);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());    //选择默认输出设备
    foreach(int count,info.supportedChannelCounts())
    {
        qDebug()<<"输出设备支持的通道数:"<<count;
    }
 
    foreach(int count,info.supportedSampleRates())
    {
        qDebug()<<"输出设备支持的采样率:"<<count;
    }
 
    foreach(int count,info.supportedSampleSizes())
    {
         qDebug()<<"输出设备支持的样本数据位数:"<<count;
    }
 
    audio = new QAudioOutput(format, this);
    audio->setBufferSize(100000);
 
   return true;
}
 
void playthread::play(QString filePath)
{
    this->filePath = filePath;
    type = control_play;
 
    if(!isRunning()) {
        this->start();
    }
}
 
void playthread::stop()
{
    if(isRunning()) {
        type = control_stop;
    }
}
 
void playthread::pause()
{
    if(isRunning()) {
        type = control_pause;
    }
}
 
 
void playthread::resume()
{
    if(this->isRunning())
    {
        type = control_resume;
    }
}
 
 
void playthread::seek(int value)
{
 
    if(this->isRunning())
    {
        seekMs = value;
        type = control_seek;
    }
}
 
void playthread::debugErr(QString prefix, int err)  //根据错误编号获取错误信息并打印
{
    char errbuf[512]={0};
 
    av_strerror(err,errbuf,sizeof(errbuf));
 
    qDebug()<<prefix<<":"<<errbuf;
 
    emit ERROR(prefix+":"+errbuf);
}
 
bool playthread::runIsBreak()      //处理控制,判断是否需要停止
{
 
    bool ret = false;
    //处理播放暂停
    if(type == control_pause)
    {
        while(type == control_pause)
        {
             audio->suspend();
             msleep(500);
        }
 
        if(type == control_resume)
        {
             audio->resume();
        }
    }
 
    if(type == control_play)    //重新播放
    {
        ret = true;
        if(audio->state()== QAudio::ActiveState)
            audio->stop();
    }
 
    if(type == control_stop)    //停止
    {
         ret = true;
         if(audio->state()== QAudio::ActiveState)
             audio->stop();
    }
    return ret;
}
 
 
void playthread::runPlay()
{
    int ret;
    int destMs, currentMs;
    if(audio==NULL) {
        emit ERROR("输出设备不支持该格式,不能播放音频");
        return;
    }
    //初始化网络库 (可以打开rtsp rtmp http 协议的流媒体视频)
    av_register_all();
    avformat_network_init();
    AVFormatContext *pFmtCtx = NULL;
    ret = avformat_open_input(&pFmtCtx, this->filePath.toLocal8Bit().data(), NULL, NULL);
    if (ret!= 0)
    {
        debugErr("avformat_open_input",ret);
        return ;
    }
    ret = avformat_find_stream_info(pFmtCtx, NULL);
    if (ret!= 0)
    {
        debugErr("avformat_find_stream_info",ret);
        return ;
    }
    int audioindex = -1;
    audioindex = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1,
                                     NULL, 0);
 
    qDebug()<<"audioindex:"<<audioindex;
    AVCodec *acodec = avcodec_find_decoder(pFmtCtx->streams[audioindex]->codecpar->codec_id);
 
    AVCodecContext *acodecCtx = avcodec_alloc_context3(acodec);
 
    avcodec_parameters_to_context(acodecCtx,pFmtCtx->streams[audioindex]->codecpar); // //初始化AVCodecContext
    ret = avcodec_open2(acodecCtx, NULL, NULL);//打开解码器,由于之前调用avcodec_alloc_context3(vcodec)初始化了vc,那么codec(第2个参数)可以填NULL
    if (ret!= 0)
    {
        debugErr("avcodec_open2",ret);
        return ;
    }
    SwrContext *swrctx = NULL;
    swrctx = swr_alloc_set_opts(swrctx,
                                av_get_default_channel_layout(2),
                                AV_SAMPLE_FMT_S16,
                                44100,
                                acodecCtx->channel_layout,
                                acodecCtx->sample_fmt,
                                acodecCtx->sample_rate,
                                NULL, NULL);
    swr_init(swrctx);
    destMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)*1000*pFmtCtx->streams[audioindex]->duration;
    qDebug()<<"码率:"<<acodecCtx->bit_rate;
    qDebug()<<"格式:"<<acodecCtx->sample_fmt;
    qDebug()<<"通道:"<<acodecCtx->channels;
    qDebug()<<"采样率:"<<acodecCtx->sample_rate;
    qDebug()<<"时长:"<<destMs;
    qDebug()<<"解码器:"<<acodec->name;
 
 
    AVPacket *packet = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    audio->stop();
    QIODevice *io = audio->start();
 
    while(1) {
        if(runIsBreak()) {
            break;
        }
        if(type == control_seek)
        {
            av_seek_frame(pFmtCtx, audioindex, seekMs/(double)1000/av_q2d(pFmtCtx->streams[audioindex]->time_base),AVSEEK_FLAG_BACKWARD);
            type = control_none;
            emit seekOk();
        }
        ret = av_read_frame(pFmtCtx, packet);
        if(ret != 0) {
            debugErr("av_read_frame",ret);
            emit duration(destMs,destMs);
            break ;
        }
 
        if(packet->stream_index == audioindex) {
            //ret = avcodec_send_frame(acodecCtx, frame);
            av_packet_unref(packet);
            if(ret != 0){
                debugErr("avcodec_send_packet",ret);
                continue ;
            }
            while(avcodec_receive_frame(acodecCtx, frame) == 0) {
                if(runIsBreak()) {
                    break;
                }
                uint8_t *data[2] = {0};
                int byteCnt = frame->nb_samples * 2 * 2;
                unsigned char *pcm = new uint8_t[byteCnt];
                data[0] = pcm;
                ret = swr_convert(swrctx,
                                  data,
                                  frame->nb_samples,
                                  (const uint8_t**)frame->data,
                                  frame->nb_samples);
                //将重采样后的data数据发送到输出设备,进行播放
                while (audio->bytesFree() < byteCnt)
                {
                    if(runIsBreak())
                        break;
                    msleep(10);
                }
 
                if(!runIsBreak())
                 io->write((const char *)pcm,byteCnt);
 
                currentMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)*1000*frame->pts;
                //qDebug()<<"时长:"<<destMs<<currentMs;
                emit duration(currentMs,destMs);
 
                delete[] pcm;
            }
        }
    }
    //释放内存
    av_frame_free(&frame);
    av_packet_free(&packet);
    swr_free(&swrctx);
    avcodec_free_context(&acodecCtx);
    avformat_close_input(&pFmtCtx);
}
 
void playthread::run()
{
 
    if(!initAudio(44100))
    {
        emit ERROR("输出设备不支持该格式,不能播放音频");
    }
 
    while(1)
    {
 
        switch(type)
        {
            case control_none: msleep(100);    break;
            case control_play : type=control_none;runPlay();  break;    //播放
            default: type=control_none;   break;
        }
    }
 
}
由于当前我的Linux还无法截图,所以无法传运行效果图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值