1.前言
     由于QAudioOutput支持的输入数据必须是原始数据,所以播放mp3,WAV,AAC等格式文件,需要解封装后才能支持播放.
     而在QT中,提供了QMediaPlayer类可以支持解封装,但是该类的解码协议都是基于平台的,如果平台自身无法播放,那么QMediaPlayer也无法播放.有兴趣的朋友可以去试试.
     所以接下来,我们使用ffmpeg+QAudioOutput来实现一个简单的音频播放器.
 
在此之前,需要学习:
 
2.界面展示
因为业余爱好,只是简单实现了大部分功能,支持播放、暂停、恢复、换歌、播放进度调节,如下图所示:
 
3.效果展示
 
4.代码流程
首先创建一个playthread线程类,然后在线程中,不断解数据,重采样,并输入到QAudioOutput的缓冲区进行播放.以及处理界面发来的命令
然后创建一个Widget界面类,通过用户操作,向playthread线程类发送控制命令.然后在playthread线程类中处理命令,命令有以下这些:
 
4.1 playthread线程类
在playthread线程类中,最核心的函数是runPlay(),该函数就是在不断的不断解数据,重采样,并输入到QAudioOutput的缓冲区进行播放.
playtherad.cpp如下所示:
#include "playthread.h"

playthread::playthread()
{
audio=NULL;
type = control_none;
} bool playthread::initAudio(int SampleRate)
{
QAudioFormat format; if(audio!=NULL)
return true; format.setSampleRate(SampleRate); //设置采样率
format.setChannelCount(); //设置通道数
format.setSampleSize(); //样本数据16位
format.setCodec("audio/pcm"); //播出格式为pcm格式
format.setByteOrder(QAudioFormat::LittleEndian); //默认小端模式
format.setSampleType(QAudioFormat::UnSignedInt); //无符号整形数 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;
// } if (!info.isFormatSupported(format))
{
qDebug()<<"输出设备不支持该格式,不能播放音频";
return false;
} audio = new QAudioOutput(format, this); audio->setBufferSize(); return true;
} void playthread::play(QString filePath)
{
this->filePath = filePath;
type = control_play; if(!this->isRunning())
{
this->start();
}
} void playthread::stop()
{ if(this->isRunning())
{
type = control_stop;
} }
void playthread::pause()
{ if(this->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[]={}; 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();
} 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 协议的流媒体视频)
avformat_network_init();
AVFormatContext *pFmtCtx=NULL;
ret = avformat_open_input(&pFmtCtx, this->filePath.toLocal8Bit().data(),NULL, NULL) ; //打开音视频文件并创建AVFormatContext结构体以及初始化.
if (ret!= )
{
debugErr("avformat_open_input",ret);
return ;
}
ret = avformat_find_stream_info(pFmtCtx, NULL); //初始化流信息
if (ret!= )
{
debugErr("avformat_find_stream_info",ret);
return ;
} int audioindex=-; audioindex = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -, -, NULL, ); qDebug()<<"audioindex:"<<audioindex; AVCodec *acodec = avcodec_find_decoder(pFmtCtx->streams[audioindex]->codecpar->codec_id);//获取codec AVCodecContext *acodecCtx = avcodec_alloc_context3(acodec); //构造AVCodecContext ,并将vcodec填入AVCodecContext中
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!= )
{
debugErr("avcodec_open2",ret);
return ;
}
SwrContext *swrctx =NULL;
swrctx=swr_alloc_set_opts(swrctx, av_get_default_channel_layout(),AV_SAMPLE_FMT_S16,,
acodecCtx->channel_layout, acodecCtx->sample_fmt,acodecCtx->sample_rate, NULL,NULL);
swr_init(swrctx); destMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)**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()
{ if(runIsBreak())
break; if(type == control_seek)
{
av_seek_frame(pFmtCtx, audioindex, seekMs/(double)/av_q2d(pFmtCtx->streams[audioindex]->time_base),AVSEEK_FLAG_BACKWARD);
type = control_none;
emit seekOk();
} ret = av_read_frame(pFmtCtx, packet);
if (ret!= )
{
debugErr("av_read_frame",ret);
emit duration(destMs,destMs);
break ;
} //解码一帧数据
ret = avcodec_send_packet(acodecCtx, packet);
av_packet_unref(packet); if (ret != )
{
debugErr("avcodec_send_packet",ret);
continue ;
} if(packet->stream_index==audioindex)
{
while( avcodec_receive_frame(acodecCtx, frame) == )
{ if(runIsBreak())
break;
uint8_t *data[] = { };
int byteCnt=frame->nb_samples * * ; unsigned char *pcm = new uint8_t[byteCnt]; //frame->nb_samples*2*2表示分配样本数据量*两通道*每通道2字节大小 data[] = pcm; //输出格式为AV_SAMPLE_FMT_S16(packet类型),所以转换后的LR两通道都存在data[0]中 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();
} if(!runIsBreak())
io->write((const char *)pcm,byteCnt); currentMs = av_q2d(pFmtCtx->streams[audioindex]->time_base)**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())
{
emit ERROR("输出设备不支持该格式,不能播放音频");
} while()
{ switch(type)
{
case control_none: msleep(); break;
case control_play : type=control_none;runPlay(); break; //播放
default: type=control_none; break;
}
} }


4.2 widget界面类

而在界面中要处理的就很简单,widget.cpp如下所示:

#include "widget.h"
#include "ui_widget.h"
#include <QDebug> 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())); void duration(long currentMs,long destMs); //播放时长 thread->start(); sliderSeeking =false;
} Widget::~Widget()
{
delete ui; thread->stop();
} void Widget::onSeekOk()
{
sliderSeeking=false;
} void Widget::onDuration(int currentMs,int destMs) //时长
{
static int currentMs1=-,destMs1=-; if(currentMs1==currentMs&&destMs1==destMs)
{
return;
} currentMs1 = currentMs;
destMs1 = destMs; qDebug()<<"onDuration:"<<currentMs<<destMs<<sliderSeeking; QString currentTime = QString("%1:%2:%3").arg(currentMs1/%,,,QChar('')).arg(currentMs1/%,,,QChar('')).arg(currentMs1/%,,,QChar('')); QString destTime = QString("%1:%2:%3").arg(destMs1/%,,,QChar('')).arg(destMs1/%,,,QChar('')).arg(destMs1/%,,,QChar('')); 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()!=)
{
ui->line_audioPath->setText(List[].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()); }
 
 
 
 
 
 

11.QT-ffmpeg+QAudioOutput实现音频播放器的更多相关文章

  1. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  2. 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  3. ffmpeg+SDL2实现的音频播放器V2.0(无杂音)

    1. 前言 目前为止,学习了并记录了ffmpeg+SDL2显示视频以及事件(event)的内容. 这篇中记录ffmpeg+SDL2播放音频,没加入事件处理. 接下来加入事件处理并继续学习音视频同步,再 ...

  4. IOS开发之简单音频播放器

    今天第一次接触IOS开发的UI部分,之前学OC的时候一直在模拟的使用Target-Action回调模式,今天算是真正的用了一次.为了熟悉一下基本控件的使用方法,和UI部分的回调,下面开发了一个特别简易 ...

  5. 与众不同 windows phone (14) - Media(媒体)之音频播放器, 视频播放器, 与 Windows Phone 的音乐和视频中心集成

    原文:与众不同 windows phone (14) - Media(媒体)之音频播放器, 视频播放器, 与 Windows Phone 的音乐和视频中心集成 [索引页][源码下载] 与众不同 win ...

  6. Unity3D音频播放器 动态装载组件

    大多数在线Unity有关如何只教程Unity在播放音乐.之后如何通过拖动它们无法继续添加音频文件 但有时在游戏中的对象要玩几个声音.这时候我们就需要使用代码控制,拖动推教程AudioClip颂值的方法 ...

  7. H.264:FFMpeg 实现简单的播放器

    H.264:FFMpeg 实现简单的播放器   FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我 ...

  8. HTML5 音频播放器-Javascript代码(短小精悍)

    直接上干货咯! //HTML5 音频播放器 lzpong 2015/01/19 var wavPlayer = function () { if(window.parent.wavPlayer) re ...

  9. 【jquery】一款不错的音频播放器——Amazing Audio Player

    前段时间分享了一款视频播放器,点击这里.今天介绍一款不错的音频播放器——Amazing Audio Player. 介绍: Amazing Audio Player 是一个使用很方便的 Windows ...

随机推荐

  1. 2020-06-21:ZK的哪些基础能力构建出了哪些上层应用?

    福哥答案2020-06-21: 福哥口诀法:数负命Ma集配分(使用场景:数据发布订阅.负载均衡.命名服务.Master 选举.集群管理.配置管理.分布式队列和分布式锁) 数据发布订阅:dubbo的rp ...

  2. JavaScript apply使用

    call 和 apply 作用: 都是为了改变某个函数运行的context上下文而存在的,为了改变函数体内部 this的指向 JavaScript函数存在定义时上下文和运行时上下文, 上下文(cont ...

  3. 写给程序员的机器学习入门 (八 补充) - 使用 GPU 训练模型

    在之前的文章中我训练模型都是使用的 CPU,因为家中黄脸婆不允许我浪费钱买电脑.终于的,附近一个废品回收站的朋友转让给我一台破烂旧电脑,所以我现在可以体验使用 GPU 训练模型了

  4. 数据库课程设计:SQL Server + Express + node.js + ejs 论坛管理系统

    前言 这是一篇对数据库课程设计的总结,这不是教程也不是指导,只是我的经验之谈,其中可能有许多错误,请小心,不要被误导.祝愿你看了这篇文章后能做出更好的设计. 我对web开发并不熟悉,而我们的课程设计只 ...

  5. MSDN 无法显示的问题 2010-03-21 21:08

    MSDN 无法显示的问题regsvr32 "C:\Program Files\Common Files\Microsoft Shared\Help\hxds.dll" .试图运行项 ...

  6. 关于word2vec我有话要说

    写在前面的话: 总结一下使用word2vec一年来的一些经验,因为自己在做的时候,很难在网上搜到word2vec的经验介绍,所以归纳出来,希望对读者有用. 这里不介绍word2vec的原理,因为原理介 ...

  7. Android Studio gridview 控件使用自定义Adapter, 九宫格items自适应全屏显示

    先看效果图,类似于支付宝首页的效果.由于九宫格显示的帖子网上已经很多,但是像这样九宫格全屏显示的例子还不是太多.本实例的需求是九宫格全屏显示,每个子view的高度是根据全屏高度三等分之后自适应高度,每 ...

  8. Python方法oslo_service.loopingcall.LoopingCallDone代码示例

    Python方法oslo_service.loopingcall.LoopingCallDone代码示例 demo: from oslo_service import loopingcall def ...

  9. html的鼠标双击,单击,移上,离开,事件捕捉,JavaScript

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. docker run 创建容器

    docker run常用命令 docker run :创建一个新的容器并运行一个命令 - 语法:docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 1.OPTI ...