[QT] some notes on using QIODevice to read audio

Keywords: Qt REST

[QT] some notes on using QIODevice to read audio

The first time I used csdn to write a blog, it's so troublesome. This markdown editor is enough for me to read for a long time...
The topic may be a little big, because here we mainly talk about the timing output and the process of writing this code.

Some preparations ahead

Environmental preparation

I'm not talking about the system environment. I'm talking about the real environment.
The best environment is that there is no one around, and then use a sound (the cheapest and the most practical kind), then take a headset or something, and the most important thing is, in any case, turn the sound to the minimum (especially the headset)!!!!!!!!!!
Prepare a music file. I'll use the decoder made by Raytheon to decode the sky city.

ffmpeg part

I used the ffmpeg transcoding to extract the audio pcm file. I don't need to talk about the specific methods. Here is Raytheon's blog. I'm also touching Raytheon's blog. The video came here, and then I saved it directly to QByteArray

//Qbytearray * play declared before_ buffer;
play_buffer->append((char*)buffer, cdp->channels * cdp->frame_size * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));

In addition, you can make an array, linked list and so on to save some time related data, which is convenient for later use when dragging progress bar.

qt part

First of all, we use ffmpeg module in the constructor of widget to read out the files and store them in memory.... Of course, when you are making a player, you can choose a more suitable time to read from the file.

Here we do a slot function of play key.

void Bricmusic::on_startplay_clicked()
{
	if (filename == NULL)
		//It's a bit sloppy to exit here...
		exit(-1);
	//QAudioQAudioOutput *streamout
	streamout = new QAudioOutput(*fmt);//QAudioFormat *fmt, obtain some relevant parameters in ffmpeg module
	//QIODevice* speaker_io
	speaker_io = streamout->start();
	//int size,time_now; size represents how many bytes of data have been read
	//time_now indicates the current time when playing (these two variables are added after the code is written)
	size = 0;
	time_now = 0;
	//Timer start
	play_timer->start();
}

Get to the point

First attempt

Because the QTimer I set is 10ms, simply calculate the amount of data is 44100 (sampling rate) × 2 (number of channels) × 16 / 8 (number of bytes) / 100 (time) = 1764

void Bricmusic::timeout_play()
{
	//Connect 1764 bytes of data to the speaker
	speaker_io->write(play_buffer->data(), 1764);
	//Move the player_buffer location
	*play_buffer = play_buffer->right(play_buffer->length() - 1764);
}

Then F5, click that button, OK, perfect, one time success!
Er, but, when the music is finished, it will give me a lot of complete noise. It may be that I have finished reading the files, but I will continue to read into the player.
So with a little change, the code will be like this.

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();
}

OK, it's over, but is there something wrong with it? Is there any problem with the last input? I didn't find out, but I don't think it's good.
So, change again.

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);
}

A little improvement

OK, that should work, but I searched other blogs and said that reading in 10 milliseconds and 10 milliseconds would get stuck,
So I changed to this.

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());
}

Since 10 milliseconds may get stuck, I can read periodsize once soon?
F5 full of confidence.
Well, there's a problem...
What kind of Indian animal is this??? It's too fast. In retrospect, it's just 1764. I've read a lot this time, but I can't. did I squeeze the data in the buffer?
Add a line of qdebug

	qDebug() << streamout->periodSize() << streamout->bytesFree()<<streamout->bufferSize();

The output is a pile of such things. We found that the 7056 is exactly 1 / 5 of the buffer 35280 (I don't understand the reason. If there is a big guy who knows, please let me know)

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

The rest of the buffer is 0, which obviously squeezes out the data. Let's add a conditional statement to determine whether there is a buffer,

if (!streamout->bytesFree())
		return;

Another F5, this time very successful.

Another improvement

When we are satisfied with the end of the plan, we find that there seems to be no way to cycle the play. Moreover, we can't support the operation of the progress bar, and then change it.
If you want to play in a loop, keep the play_buffer, we can copy a QByteArray, but we can't copy it completely. It's a waste of resources. Let's get a toolmaker to come out and play a copy.

void Bricmusic::timeout_play()
{
	//Tool man
	QByteArray tempbuffer;
	//Insert the current time of a playback, which can correspond to the time obtained from ffmpeg.
	time_now += 10;
	if (!streamout->bytesFree())
		return;
	if (play_buffer->size() - size >= streamout->periodSize())
	{
		//mid() starts from size and intercepts streamout - > periodsize() bytes
		tempbuffer = play_buffer->mid(size, streamout->periodSize());
		size += streamout->periodSize();
		//It's not the same as the previous one. It's the whole QByteArray
		//It can also be written as speaker_ io->write( tempbuffer.data () tempbuffer.size ());
		speaker_io->write(tempbuffer);
	}
	else
	{
		//It's not the same as above. It's from size to tail
		tempbuffer = play_buffer->mid(size);
		size = play_buffer->size();
		speaker_io->write(tempbuffer);
		//Clear size for next use
		size = 0;
		time_now = 0;  //Loop Playback
		//play_ Timer - > stop(); / / end
	}
}

When the program writes this, it's probably finished. As for drag progress bar, use time_now you can do it. Change to the size corresponding to time.

Finally, simply add a QLabel to represent time, and then come up with such a hard core player interface.

Posted by Patch^ on Sat, 20 Jun 2020 00:56:53 -0700