参考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还无法截图,所以无法传运行效果图