SDL_AudioSpec结构中call_back函数解析

本文详细解析了SDL_AudioSpec结构中的call_back函数实现原理及工作流程。介绍了如何通过回调函数填充音频缓冲区,并阐述了解码过程及数据拷贝机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SDL_AudioSpec结构中call_back函数解析

参考教程http://dranger.com/ffmpeg/tutorial03.html

void audio_callback(void *userdata, Uint8 *stream, int len)

第一个参数userdata是AVCodecContext,为了获取AVPacket传入AVCodecContext结构体,用于解码

第二个参数stream指向需要填充的音频缓冲区

第三个参数len,表示音频缓存区的大小

对于何时调用callback回调函数,文档指出:Callback function for filling the audio buffer,应该是音频设备准备好之后开始定时通过callback取回数据到stream中。具体代码注释如下

/*
*
第一个参数userdata是AVCodecContext,为了获取AVPacket传入AVCodecContext结构体,用于解码

第二个参数stream指向需要填充的音频缓冲区

第三个参数len,表示音频缓存区的大小
*
*/
void audio_callback(void *userdata, Uint8 *stream, int len) {

	AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
	int len1, audio_size;

	static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
	static unsigned int audio_buf_size = 0;
	static unsigned int audio_buf_index = 0;

	while (len > 0) {
		//通过while循环判断缓冲区大小是否大于0,如果len > 0,说明缓存区stream需要填充数据,那么就判断
		if (audio_buf_index >= audio_buf_size) {
			/* We have already sent all our data; get more */
			//audio缓冲区拷贝索引指针指向buf结尾,说明缓冲区所有数据都已拷贝到stream中,需要重新解码获取帧数据
			audio_size = audio_decode_frame(aCodecCtx, audio_buf, audio_buf_size);
			if (audio_size < 0) {
				/* If error, output silence */
				audio_buf_size = 1024; // arbitrary?
				memset(audio_buf, 0, audio_buf_size);
			}
			else {
				//解码成功,则记录buf大小
				audio_buf_size = audio_size;
			}
			//重置buf索引为0
			audio_buf_index = 0;
		}
		//记录buf数据字节总数
		len1 = audio_buf_size - audio_buf_index;
		if (len1 > len)  //如果buf数据大于缓存区大小
			len1 = len; //先拷贝缓冲区大小的数据
		memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1); //拷贝固定大小的数据
		len -= len1;  //记录下次需要拷贝的字节数
		stream += len1; //stream后移len1字节
		audio_buf_index += len1; //buf索引值后移len1字节
	}
}

拷贝过程如下:

在这里插入图片描述

上图中缓冲区大小是4224,实际情况可能不同,每次判断缓存区是否填满,如果没有填满,则检查解码得到的buf是否用完,如果没有用完,则继续从buf中拷贝到stream,并记录已拷贝的字节数,移动stream指针以及索引,记录stream缓冲区剩余要拷贝的字节数,如果剩余字节数为0,则stream已拷贝完成。audio_callback函数中使用了静态变量,静态变量的生命周期是整个程序结束,因此阅读代码时注意。

/*
*
*第一个参数 aCodecCtx,用于解码获取帧数据
*第二个参数 audio_buf,用户存储解码后的音频数据
*第三个参数 buf_size是audio_buf的大小
*/
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {

	static AVPacket pkt;
	static uint8_t *audio_pkt_data = NULL;
	static int audio_pkt_size = 0;
	static AVFrame frame;

	int len1, data_size = 0;

	for (;;) {
		//audio_pkt_size表示需要解码的AVPacket大小,每次解码后会减去相应的avcodec_decode_audio4返回值,
		//判断是否一次能够解码完成,否则可能分多次解码
		while (audio_pkt_size > 0) {
			int got_frame = 0;
			try
			{
				len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
				if (len1 < 0) {
					/* if error, skip frame */
					audio_pkt_size = 0;
					break;
				}
			}
			catch (...)
			{
				int error;
				AVERROR(error);
			}
			//audio_pkt_data是一个指针,指向pkt.data
			audio_pkt_data += len1;
			//audio_pkt_size记录剩余需要解码的大小
			audio_pkt_size -= len1;
			//获取帧数据成功
			if (got_frame)
			{
				//获取拷贝buf的大小
				data_size =
					av_samples_get_buffer_size
					(
						NULL,
						aCodecCtx->channels,
						frame.nb_samples,
						aCodecCtx->sample_fmt,
						1
					);
				//把frame.data中的数据拷贝到audio_buf
				memcpy(audio_buf, frame.data[0], data_size);
			}
			if (data_size <= 0) {
				/* No data yet, get more frames */
				continue;
			}
			/* We have data, return it and come back for more later */
			return data_size;
		}
		//只有下次进入audio_decode_frame函数中,并且上次的解码完成,才会释放pkt
		if (pkt.data)
			av_free_packet(&pkt);

		if (quit) {
			return -1;
		}
		//获取pkt数据
		if (packet_queue_get(&audioq, &pkt, 1) < 0) {
			return -1;
		}
		audio_pkt_data = pkt.data;
		audio_pkt_size = pkt.size;
	}
}
SDL_AudioSpec结构体中的callback字段是一个函数指针,用于指定音频处理回调函数。回调函数的原型如下: ```c typedef void (*SDL_AudioCallback)(void *userdata, Uint8 *stream, int len); ``` 其中,`userdata`参数是传递给回调函数的用户数据指针,`stream`参数是指向音频缓冲区的指针,`len`是音频缓冲区的字节数。 在回调函数中,你可以对音频缓冲区进行读取和写入操作。回调函数将在每次需要填充音频缓冲区时被调用,因此你可以在这里以实时的方式处理音频数据。 下面是一个简单的例子,演示了如何使用SDL_AudioSpec结构体中的callback字段: ```c #include <SDL2/SDL.h> // 音频处理回调函数 void audio_callback(void *userdata, Uint8 *stream, int len) { // 从用户数据中获取音频数据 float *samples = (float*)userdata; // 将音频数据写入到音频缓冲区中 SDL_memcpy(stream, samples, len); } int main(int argc, char* argv[]) { SDL_Init(SDL_INIT_AUDIO); SDL_AudioSpec spec; spec.freq = 44100; spec.format = AUDIO_F32; spec.channels = 2; spec.samples = 1024; spec.callback = audio_callback; // 创建音频设备 SDL_AudioDeviceID device = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0); // 创建音频数据 float samples[1024 * 2]; for (int i = 0; i < 1024 * 2; i++) { samples[i] = sinf(i * 2 * M_PI / 44100 * 440); } // 开始播放音频 SDL_PauseAudioDevice(device, 0); // 等待一段时间 SDL_Delay(5000); // 停止播放音频 SDL_CloseAudioDevice(device); SDL_Quit(); return 0; } ``` 在这个例子中,我们首先使用SDL_Init函数初始化SDL库。然后,我们定义了一个SDL_AudioSpec结构体,并设置了音频参数和回调函数。接着,我们使用SDL_OpenAudioDevice函数创建音频设备,并将音频数据写入到缓冲区中。最后,我们使用SDL_PauseAudioDevice函数开始播放音频,并使用SDL_Delay函数等待一段时间,然后停止播放音频并关闭音频设备。 注意,这只是一个非常简单的例子,实际的音频处理可能需要更复杂的算法。同时,由于回调函数需要在实时处理音频数据,因此需要尽可能地避免阻塞操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

feng_blog6688

只需一个赞,谢谢你的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值