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. Python中 *args 和 **kwargs 的含义?

    答:在python中,*args和**kwargs通常使用在函数定义里.*args 和 **kwargs 都允许你给函数传不定数量的参数,即使在定义函数的时候不知道调用者会传递几个参数.ps: *ar ...

  2. java this关键字调用构造方法

    一 this调用构造方法 构造方法之间的调用,可以通过this关键字来完成. 格式: this(参数列表); 构造方法的调用举例: class Person { // Person的成员属性 priv ...

  3. TCP/IP速记

    目录 网络协议 OSI七层模型和TCP/IP五层模型 TCP/IP五层模型 TCP的三次握手和四次挥手 三次握手进行连接 四次挥手断开连接 TCP连接的特点 TCP是如何保证安全可靠的 UDP连接的特 ...

  4. DRF内置认证组件之自定义认证系统

    自定义token认证 我们知道,在django项目中不管路由以及对应的视图类是如何写的,都会走到 dispatch 方法,进行路由分发, 在阅读 APIView类中的dispatch 方法的源码中,有 ...

  5. Kerberos认证原理及基于Kerberos认证的NFS文件共享

    目录 Kerberos认证原理 简介 client访问server过程 一.Authentication Service Exchange (AS Exchange) 二.Ticket Grantin ...

  6. 已废弃_CSDN慕零的黑夜-头条-第一期(必问)[导读:]1.CSDN必问赏金流向何方 2.CSDN必问偷偷做的手脚 3.CSDN必问靠谱吗 4.关于钱于回答的平衡问题:一美元拍卖骗局qq3461896724

    [本文有已知的链接差错,懒得改了] 本期是关于CSDN 必问 (biwen.csdn.net)的内容,欢迎评论文末,文中插入有 小姐姐 img(附py代码,1.49G) + coding资料 哟~~~ ...

  7. XSS的构造技巧

    XSS的构造技巧 By:Mirror王宇阳 E-mail:2821319009@qq.com 本文为长期持续维护,一旦遇到新的技术技巧将及时更新本文内容 非常的欢迎技术大佬纠正并补充,也欢迎技术同僚讨 ...

  8. markdown使用手册

    github上找到了一个挺全面的项目,mark一下. https://github.com/nicejade/nice-front-end-tutorial/blob/master/tutorial/ ...

  9. 【故障公告】阿里云 RDS 数据库突发 CPU 近 100% 引发全站故障

    今天晚上9点我们收到阿里云的告警通知: [阿里云监控]华东1(杭州)-云数据库RDS版<cnblogsdb> [instanceId=xxx] 于21:00 发生告警, 前往诊断 CPU使 ...

  10. 冒泡排序(Bubble Sorting)

    基本介绍 时间复杂度O(n^2) 冒泡排序(Bubble Sorting)的基本思想是:通过对待 排序序列从前向后(从下标较小的元素开始),依次比较 相邻元素的值,若发现逆序则交换,使值较大 的元素逐 ...