[QT]有关利用QIODevice读取音频的一点笔记心得
头一次用csdn写博客,原来这么麻烦的啊,这markdown编辑器够我看好久。。。
题目可能说的有点大,因为这里主要聊聊定时输出和自己写这段代码的一些过程。
前面的一些准备
环境的准备
我说的不是系统环境什么的,我说的是现实环境。
最好的环境是周围没有别人,然后用个音响(最便宜最皮实的那种),其次呢就是拿个耳机什么的,最最最重要的是,无论如何,把声音调到最小(尤其耳机)!!!!!!!!!!
准备个音乐文件,我这里直接用的雷神做的解码器里面的 天空之城 了。
ffmpeg部分
前面我是用的ffmpeg转码把音频的pcm文件提取出来,具体的方法不讲了,这里指路雷神的博客,我也是摸着雷神的博客,视频走过来的,然后直接存到QByteArray
//前面有声明 QByteArray* play_buffer;
play_buffer->append((char*)buffer, cdp->channels * cdp->frame_size * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
另外这里可以再弄个数组、链表之类的保存一些有关时间的数据,方便以后拖拽进度条的时候使用。
qt部分
首先在widget的构造函数中利用ffmpeg模块把文件读出来,全存到内存里面(没想到有什么好的办法既可以省内存,又可以拖进度条,就。。。。一股脑全读进去了),当然,在做播放器的时候,这个部分可以挑一个更合适的时候从文件里面读取。
这里做了个播放键的槽函数。
void Bricmusic::on_startplay_clicked()
{
if (filename == NULL)
//这里exit有点草率。。。
exit(-1);
//QAudioQAudioOutput *streamout
streamout = new QAudioOutput(*fmt);//QAudioFormat *fmt,在ffmpeg模块中获得一些相关的参数
//QIODevice* speaker_io
speaker_io = streamout->start();
//int size,time_now; size代表已经读取过多少个字节的数据
//time_now表示播放时的当前时间(这两个是在写代码的途中后加的变量)
size = 0;
time_now = 0;
//计时器开始
play_timer->start();
}
步入正题
第一次尝试
因为我设置的QTimer是10ms的,简单算下数据量是44100(采样率)*2(声道数)*16/8(字节数)/100(时间)=1764
void Bricmusic::timeout_play()
{
//往扬声器里面怼1764个字节的数据
speaker_io->write(play_buffer->data(), 1764);
//挪一下player_buffer位置
*play_buffer = play_buffer->right(play_buffer->length() - 1764);
}
然后F5,点一下那个按钮,好的,非常完美,一次成功!
呃,但是,但是,这音乐放完了,就给我放出一堆完完全全的噪音,这可能是文件读完了,但是还是继续往播放器里面读东西。
那么我们稍加改动下,代码就成了这样。
void Bricmusic::timeout_play()
{
qDebug() << streamout->periodSize() << streamout->bytesFree();
speaker_io->write(play_buffer->data(), 1764);
*play_buffer = play_buffer->right(play_buffer->length() - 1764);
if (!play_buffer->size())
play_timer->stop();
}
好的,该结束就结束了,但是,会不会有些不对呢,最后一次输入会不会有问题?我是没有发现的,但我觉得这样不好。
于是,再改。
void Bricmusic::timeout_play()
{
if (play_buffer->size() >= 1764)
speaker_io->write(play_buffer->data(), 1764);
else
{
speaker_io->write(play_buffer->data(), play_buffer->size());
play_timer->stop();
}
*play_buffer = play_buffer->right(play_buffer->length() - 1764);
}
一点改进
好的,这应该就能用了吧,但我搜别的博客说这样10毫秒10毫秒的读会卡顿,
于是我就改成了这样。
void Bricmusic::timeout_play()
{
if (play_buffer->size() >= streamout->periodSize())
speaker_io->write(play_buffer->data(), streamout->periodSize());
else
{
speaker_io->write(play_buffer->data(), play_buffer->size());
play_timer->stop();
}
*play_buffer = play_buffer->right(play_buffer->length() - streamout->periodSize());
}
既然10毫秒可能会卡顿,那我一次读个periodsize的不就没问题了?
信心满满的F5一下。
好了,问题出现了。。。
这是什么印度风情的鬼畜???曲速太快了啊。回想了一下,刚刚1764就可以,这次读多了,反倒不行了,是不是把缓冲区的数据给挤了?
加一行qdebug
qDebug() << streamout->periodSize() << streamout->bytesFree()<<streamout->bufferSize();
输出的是一堆这样的东西,我们发现这个7056正好是缓冲区35280的1/5(我是不懂这个原因的,如果有大佬知道的话,恳请告知)
7056 7056 35280
7056 0 35280
7056 0 35280
7056 0 35280
7056 7056 35280
7056 0 35280
7056 0 35280
7056 0 35280
7056 7056 35280
这缓冲区剩余的都是0了,明显是把数据给挤掉了。那我们加个条件语句,判断下还有没有缓冲区吧,
if (!streamout->bytesFree())
return;
又一次的F5,这次非常成功。
再一次改进
当我们心满意足的打算结束的时候,我们发现,这样好像没有办法循环播放啊,而且,也不能支持进度条的操作,再改。
既然想循环播放,那就要留着play_buffer
,我们可以复制一个QByteArray
出来,但完全复制肯定是不行的,有点浪费资源,我们弄个工具人出来,播一段复制一段。
void Bricmusic::timeout_play()
{
//工具人
QByteArray tempbuffer;
//插入一个播放的当前时间,可以对应从ffmpeg获取的的时间。
time_now += 10;
if (!streamout->bytesFree())
return;
if (play_buffer->size() - size >= streamout->periodSize())
{
//mid()从size开始截取,截取streamout->periodSize()个字节
tempbuffer = play_buffer->mid(size, streamout->periodSize());
size += streamout->periodSize();
//这里跟前面不一样,是直接用的整个QByteArray
//当然也可以写成speaker_io->write(tempbuffer.data(),tempbuffer.size());
speaker_io->write(tempbuffer);
}
else
{
//跟上面也不一样,这是从size截取到尾部
tempbuffer = play_buffer->mid(size);
size = play_buffer->size();
speaker_io->write(tempbuffer);
//清空size方便便下一次用
size = 0;
time_now = 0; //循环播放
//play_timer->stop(); //结束
}
}
程序写到这,大概也就算完成了,至于拖拽进度条这种东西,利用time_now
就可以做,改到time对应的size就可以了。
最后,简单的加个QLabel表示时间,然后就弄出了这么个硬核的播放器界面。