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