Beispiel #1
0
int cellAudioInit()
{
	cellAudio.Warning("cellAudioInit()");

	if (m_config.m_is_audio_initialized)
	{
		return CELL_AUDIO_ERROR_ALREADY_INIT;
	}

	m_config.m_is_audio_initialized = true;
	m_config.counter = 0;

	// alloc memory
	m_config.m_buffer = Memory.Alloc(128 * 1024 * m_config.AUDIO_PORT_COUNT, 1024); 
	memset(Memory + m_config.m_buffer, 0, 128 * 1024 * m_config.AUDIO_PORT_COUNT);
	m_config.m_indexes = Memory.Alloc(sizeof(u64) * m_config.AUDIO_PORT_COUNT, 16);
	memset(Memory + m_config.m_indexes, 0, sizeof(u64) * m_config.AUDIO_PORT_COUNT);

	thread t("Audio Thread", []()
		{
			AudioDumper m_dump(2); // WAV file header (stereo)
		
			if (Ini.AudioDumpToFile.GetValue() && !m_dump.Init())
			{
				ConLog.Error("Audio aborted: cannot create file!");
				return;
			}

			ConLog.Write("Audio started");

			m_config.start_time = get_system_time();

			if (Ini.AudioDumpToFile.GetValue())
				m_dump.WriteHeader();

			float buffer[2*256]; // buffer for 2 channels
			be_t<float> buffer2[8*256]; // buffer for 8 channels (max count)
			//u16 oal_buffer[2*256]; // buffer for OpenAL

			uint oal_buffer_offset = 0;
			uint oal_buffer_size = 2 * 256;
			std::unique_ptr<u16[]> oal_buffer(new u16[oal_buffer_size]);

			memset(buffer, 0, sizeof(buffer));
			memset(buffer2, 0, sizeof(buffer2));
			memset(oal_buffer.get(), 0, oal_buffer_size * sizeof(u16));

			if(Ini.AudioOutMode.GetValue() == 1)
			{
				m_audio_out->Init();
				m_audio_out->Open(oal_buffer.get(), oal_buffer_size*sizeof(u16));
			}

			while (m_config.m_is_audio_initialized)
			{
				if (Emu.IsStopped())
				{
					ConLog.Warning("Audio aborted");
					goto abort;
				}

				// TODO: send beforemix event (in ~2,6 ms before mixing)

				// Sleep(5); // precise time of sleeping: 5,(3) ms (or 256/48000 sec)
				if (m_config.counter * 256000000 / 48000 >= get_system_time() - m_config.start_time)
				{
					Sleep(1);
					continue;
				}

				m_config.counter++;

				if (Emu.IsPaused())
				{
					continue;
				}

				bool first_mix = true;

				// MIX:
				for (u32 i = 0; i < m_config.AUDIO_PORT_COUNT; i++)
				{
					if (!m_config.m_ports[i].m_is_audio_port_started) continue;

					AudioPortConfig& port = m_config.m_ports[i];
					mem64_t index(m_config.m_indexes + i * sizeof(u64));

					const u32 block_size = port.channel * 256;

					u32 position = port.tag % port.block; // old value

					u32 buf_addr = m_config.m_buffer + (i * 128 * 1024) + (position * block_size * sizeof(float));

					memcpy(buffer2, Memory + buf_addr, block_size * sizeof(float));
					memset(Memory + buf_addr, 0, block_size * sizeof(float));

					{
						SMutexGeneralLocker lock(port.m_mutex);
						port.counter = m_config.counter;
						port.tag++; // absolute index of block that will be read
						index = (position + 1) % port.block; // write new value
					}

					if (first_mix)
					{
						for (u32 i = 0; i < (sizeof(buffer) / sizeof(float)); i++)
						{
							// reverse byte order (TODO: use port.m_param.level)
							buffer[i] = buffer2[i];

							// convert the data from float to u16
							assert(buffer[i] >= -4.0f && buffer[i] <= 4.0f);
							oal_buffer[oal_buffer_offset + i] = (u16)(buffer[i] * ((1 << 13) - 1));
						}

						first_mix = false;
					}
					else
					{
						for (u32 i = 0; i < (sizeof(buffer) / sizeof(float)); i++)
						{
							buffer[i] = (buffer[i] + buffer2[i]) * 0.5; // TODO: valid mixing

							// convert the data from float to u16
							assert(buffer[i] >= -4.0f && buffer[i] <= 4.0f);
							oal_buffer[oal_buffer_offset + i] = (u16)(buffer[i] * ((1 << 13) - 1));
						}
					}
				}

				// send aftermix event (normal audio event)
				// TODO: check event source
				Emu.GetEventManager().SendEvent(m_config.event_key, 0x10103000e010e07, 0, 0, 0);

				oal_buffer_offset += sizeof(buffer) / sizeof(float);

				if(oal_buffer_offset >= oal_buffer_size)
				{
					m_audio_out->AddData(oal_buffer.get(), oal_buffer_offset * sizeof(u16));

					oal_buffer_offset = 0;
				}

				if(Ini.AudioDumpToFile.GetValue())
				{
					if (m_dump.WriteData(&buffer, sizeof(buffer)) != sizeof(buffer)) // write file data
					{
						ConLog.Error("Port aborted: cannot write file!");
						goto abort;
					}

					m_dump.UpdateHeader(sizeof(buffer));
				}
			}
			ConLog.Write("Audio finished");
abort:
			if(Ini.AudioDumpToFile.GetValue())
				m_dump.Finalize();

			m_config.m_is_audio_finalized = true;
		});
	t.detach();

	return CELL_OK;
}
Beispiel #2
0
void audio_thread::operator()()
{
	thread_ctrl::set_native_priority(1);

	AudioDumper m_dump(g_cfg.audio.dump_to_file ? 2 : 0); // Init AudioDumper for 2 channels if enabled

	float buf2ch[2 * BUFFER_SIZE]{}; // intermediate buffer for 2 channels
	float buf8ch[8 * BUFFER_SIZE]{}; // intermediate buffer for 8 channels

	const u32 buf_sz = BUFFER_SIZE * (g_cfg.audio.convert_to_u16 ? 2 : 4) * (g_cfg.audio.downmix_to_2ch ? 2 : 8);

	std::unique_ptr<float[]> out_buffer[BUFFER_NUM];

	for (u32 i = 0; i < BUFFER_NUM; i++)
	{
		out_buffer[i].reset(new float[8 * BUFFER_SIZE] {});
	}

	const auto audio = Emu.GetCallbacks().get_audio();
	audio->Open(buf8ch, buf_sz);

	while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped())
	{
		if (Emu.IsPaused())
		{
			thread_ctrl::wait_for(1000); // hack
			continue;
		}

		const u64 stamp0 = get_system_time();

		const u64 time_pos = stamp0 - start_time - Emu.GetPauseTime();

		// TODO: send beforemix event (in ~2,6 ms before mixing)

		// precise time of sleeping: 5,(3) ms (or 256/48000 sec)
		const u64 expected_time = m_counter * AUDIO_SAMPLES * 1000000 / 48000;
		if (expected_time >= time_pos)
		{
			thread_ctrl::wait_for(1000); // hack
			continue;
		}

		m_counter++;

		const u32 out_pos = m_counter % BUFFER_NUM;

		bool first_mix = true;

		// mixing:
		for (auto& port : ports)
		{
			if (port.state != audio_port_state::started) continue;

			const u32 block_size = port.channel * AUDIO_SAMPLES;
			const u32 position = port.tag % port.block; // old value
			const u32 buf_addr = port.addr.addr() + position * block_size * sizeof(float);

			auto buf = vm::_ptr<f32>(buf_addr);

			static const float k = 1.0f; // may be 1.0f
			const float& m = port.level;

			auto step_volume = [](audio_port& port) // part of cellAudioSetPortLevel functionality
			{
				const auto param = port.level_set.load();

				if (param.inc != 0.0f)
				{
					port.level += param.inc;
					const bool dec = param.inc < 0.0f;

					if ((!dec && param.value - port.level <= 0.0f) || (dec && param.value - port.level >= 0.0f))
					{
						port.level = param.value;
						port.level_set.compare_and_swap(param, { param.value, 0.0f });
					}
				}
			};

			if (port.channel == 2)
			{
				if (first_mix)
				{
					for (u32 i = 0; i < std::size(buf2ch); i += 2)
					{
						step_volume(port);

						// reverse byte order
						const float left = buf[i + 0] * m;
						const float right = buf[i + 1] * m;

						buf2ch[i + 0] = left;
						buf2ch[i + 1] = right;

						buf8ch[i * 4 + 0] = left;
						buf8ch[i * 4 + 1] = right;
						buf8ch[i * 4 + 2] = 0.0f;
						buf8ch[i * 4 + 3] = 0.0f;
						buf8ch[i * 4 + 4] = 0.0f;
						buf8ch[i * 4 + 5] = 0.0f;
						buf8ch[i * 4 + 6] = 0.0f;
						buf8ch[i * 4 + 7] = 0.0f;
					}
					first_mix = false;
				}
				else
				{
					for (u32 i = 0; i < std::size(buf2ch); i += 2)
					{
						step_volume(port);

						const float left = buf[i + 0] * m;
						const float right = buf[i + 1] * m;

						buf2ch[i + 0] += left;
						buf2ch[i + 1] += right;

						buf8ch[i * 4 + 0] += left;
						buf8ch[i * 4 + 1] += right;
					}
				}
			}
			else if (port.channel == 8)
			{
				if (first_mix)
				{
					for (u32 i = 0; i < std::size(buf2ch); i += 2)
					{
						step_volume(port);

						const float left = buf[i * 4 + 0] * m;
						const float right = buf[i * 4 + 1] * m;
						const float center = buf[i * 4 + 2] * m;
						const float low_freq = buf[i * 4 + 3] * m;
						const float rear_left = buf[i * 4 + 4] * m;
						const float rear_right = buf[i * 4 + 5] * m;
						const float side_left = buf[i * 4 + 6] * m;
						const float side_right = buf[i * 4 + 7] * m;

						const float mid = (center + low_freq) * 0.708f;
						buf2ch[i + 0] = (left + rear_left + side_left + mid) * k;
						buf2ch[i + 1] = (right + rear_right + side_right + mid) * k;

						buf8ch[i * 4 + 0] = left;
						buf8ch[i * 4 + 1] = right;
						buf8ch[i * 4 + 2] = center;
						buf8ch[i * 4 + 3] = low_freq;
						buf8ch[i * 4 + 4] = rear_left;
						buf8ch[i * 4 + 5] = rear_right;
						buf8ch[i * 4 + 6] = side_left;
						buf8ch[i * 4 + 7] = side_right;
					}
					first_mix = false;
				}
				else
				{
					for (u32 i = 0; i < std::size(buf2ch); i += 2)
					{
						step_volume(port);

						const float left = buf[i * 4 + 0] * m;
						const float right = buf[i * 4 + 1] * m;
						const float center = buf[i * 4 + 2] * m;
						const float low_freq = buf[i * 4 + 3] * m;
						const float rear_left = buf[i * 4 + 4] * m;
						const float rear_right = buf[i * 4 + 5] * m;
						const float side_left = buf[i * 4 + 6] * m;
						const float side_right = buf[i * 4 + 7] * m;

						const float mid = (center + low_freq) * 0.708f;
						buf2ch[i + 0] += (left + rear_left + side_left + mid) * k;
						buf2ch[i + 1] += (right + rear_right + side_right + mid) * k;

						buf8ch[i * 4 + 0] += left;
						buf8ch[i * 4 + 1] += right;
						buf8ch[i * 4 + 2] += center;
						buf8ch[i * 4 + 3] += low_freq;
						buf8ch[i * 4 + 4] += rear_left;
						buf8ch[i * 4 + 5] += rear_right;
						buf8ch[i * 4 + 6] += side_left;
						buf8ch[i * 4 + 7] += side_right;
					}
				}
			}
			else
			{
				fmt::throw_exception("Unknown channel count (port=%u, channel=%d)" HERE, port.number, port.channel);
			}

			memset(buf, 0, block_size * sizeof(float));
		}


		if (!first_mix)
		{
			// Copy output data (2ch or 8ch)
			if (g_cfg.audio.downmix_to_2ch)
			{
				for (u32 i = 0; i < std::size(buf2ch); i++)
				{
					out_buffer[out_pos][i] = buf2ch[i];
				}
			}
			else
			{
				for (u32 i = 0; i < std::size(buf8ch); i++)
				{
					out_buffer[out_pos][i] = buf8ch[i];
				}
			}
		}

		const u64 stamp1 = get_system_time();

		if (first_mix)
		{
			std::memset(out_buffer[out_pos].get(), 0, 8 * BUFFER_SIZE * sizeof(float));
		}

		if (g_cfg.audio.convert_to_u16)
		{
			// convert the data from float to u16 with clipping:
			// 2x MULPS
			// 2x MAXPS (optional)
			// 2x MINPS (optional)
			// 2x CVTPS2DQ (converts float to s32)
			// PACKSSDW (converts s32 to s16 with signed saturation)

			__m128i buf_u16[BUFFER_SIZE];

			for (size_t i = 0; i < 8 * BUFFER_SIZE; i += 8)
			{
				const auto scale = _mm_set1_ps(0x8000);
				buf_u16[i / 8] = _mm_packs_epi32(
					_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer[out_pos].get() + i), scale)),
					_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer[out_pos].get() + i + 4), scale)));
			}

			audio->AddData(buf_u16, buf_sz);
		}
		else
		{
			audio->AddData(out_buffer[out_pos].get(), buf_sz);
		}

		const u64 stamp2 = get_system_time();

		{
			// update indices:

			for (u32 i = 0; i < AUDIO_PORT_COUNT; i++)
			{
				audio_port& port = ports[i];

				if (port.state != audio_port_state::started) continue;

				u32 position = port.tag % port.block; // old value
				port.counter = m_counter;
				port.tag++; // absolute index of block that will be read
				m_indexes[i] = (position + 1) % port.block; // write new value
			}

			// send aftermix event (normal audio event)

			auto _locked = g_idm->lock<named_thread<audio_thread>>(0);

			for (u64 key : keys)
			{
				// TODO: move out of the lock scope
				if (auto queue = lv2_event_queue::find(key))
				{
					queue->send(0, 0, 0, 0); // TODO: check arguments
				}
			}
		}

		const u64 stamp3 = get_system_time();

		switch (m_dump.GetCh())
		{
		case 2: m_dump.WriteData(&buf2ch, sizeof(buf2ch)); break; // write file data (2 ch)
		case 8: m_dump.WriteData(&buf8ch, sizeof(buf8ch)); break; // write file data (8 ch)
		}

		cellAudio.trace("Audio perf: (access=%d, AddData=%d, events=%d, dump=%d)",
			stamp1 - stamp0, stamp2 - stamp1, stamp3 - stamp2, get_system_time() - stamp3);
	}
}