[QT]有关利用QIODevice读取音频的一点笔记心得

[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表示时间,然后就弄出了这么个硬核的播放器界面。
[QT]有关利用QIODevice读取音频的一点笔记心得

上一篇:WPF 已知问题 BitmapDecoder.Create 不支持传入 Asynchronous 的文件流


下一篇:不通过删除重建方式,重置序列值的简单方法