forked from henkelmax/replay-voice-chat
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
305 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
- Update to Plasmo Voice 2.1.0. This version is not compatible with PV 2.0.x. | ||
- Pitch is now used to control the playback speed of the source. | ||
- Voice audio render to AAC format using OpenAL loopback device. | ||
Rendered audio is saved in the same location and the same name (but with .aac extension) as the video. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
version=2.1.0 | ||
version=2.1.1 | ||
|
||
org.gradle.jvmargs=-Xmx4G | ||
kotlin.stdlib.default.dependency=false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
versions/src/main/java/su/plo/replayvoice/mixin/MixinAlOutputDevice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package su.plo.replayvoice.mixin; | ||
|
||
import org.lwjgl.openal.ALC10; | ||
import org.lwjgl.openal.ALC11; | ||
import org.lwjgl.openal.SOFTLoopback; | ||
import org.lwjgl.system.MemoryStack; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
import org.spongepowered.asm.mixin.injection.Inject; | ||
import org.spongepowered.asm.mixin.injection.Redirect; | ||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; | ||
import su.plo.replayvoice.render.VoiceAudioRender; | ||
import su.plo.voice.client.audio.device.AlOutputDevice; | ||
|
||
import java.nio.Buffer; | ||
import java.nio.IntBuffer; | ||
|
||
@Mixin(value = AlOutputDevice.class, remap = false) | ||
public final class MixinAlOutputDevice { | ||
|
||
@Inject(method = "openDevice", at = @At("HEAD"), cancellable = true) | ||
private void openDevice(String deviceName, CallbackInfoReturnable<Long> cir) { | ||
if (!VoiceAudioRender.isRendering()) return; | ||
|
||
long devicePointer = SOFTLoopback.alcLoopbackOpenDeviceSOFT((String) null); | ||
cir.setReturnValue(devicePointer); | ||
} | ||
|
||
@Redirect(method = "openSync", at = @At(value = "INVOKE", target = "Lorg/lwjgl/openal/ALC11;alcCreateContext(JLjava/nio/IntBuffer;)J")) | ||
private long createContext(long devicePointer, IntBuffer attrList) { | ||
if (!VoiceAudioRender.isRendering()) { | ||
return ALC11.alcCreateContext(devicePointer, attrList); | ||
} | ||
|
||
try (MemoryStack memoryStack = MemoryStack.stackPush()) { | ||
IntBuffer intBuffer = memoryStack.callocInt(7) | ||
.put(ALC10.ALC_FREQUENCY).put(48000) | ||
.put(SOFTLoopback.ALC_FORMAT_CHANNELS_SOFT).put(SOFTLoopback.ALC_STEREO_SOFT) | ||
.put(SOFTLoopback.ALC_FORMAT_TYPE_SOFT).put(SOFTLoopback.ALC_SHORT_SOFT) | ||
.put(0); | ||
((Buffer) intBuffer).flip(); | ||
return ALC10.alcCreateContext(devicePointer, intBuffer); | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
versions/src/main/java/su/plo/replayvoice/mixin/MixinProcessTask.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package su.plo.replayvoice.mixin; | ||
|
||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
import org.spongepowered.asm.mixin.injection.Inject; | ||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; | ||
import su.plo.replayvoice.render.VoiceAudioRender; | ||
|
||
@Mixin(targets = "com.replaymod.render.rendering.Pipeline$ProcessTask", remap = false) | ||
public final class MixinProcessTask { | ||
|
||
@Inject(method = "run", at = @At(value = "INVOKE", target = "Lcom/replaymod/render/rendering/FrameConsumer;consume(Ljava/util/Map;)V", shift = At.Shift.BEFORE)) | ||
public void run(CallbackInfo ci) { | ||
VoiceAudioRender.AUDIO_RENDER.render(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
versions/src/main/java/su/plo/replayvoice/render/FfmpegAudioWriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package su.plo.replayvoice.render; | ||
|
||
import com.replaymod.lib.org.apache.commons.exec.CommandLine; | ||
import org.jetbrains.annotations.NotNull; | ||
import su.plo.replayvoice.ReplayVoiceAddon; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public final class FfmpegAudioWriter implements AutoCloseable { | ||
|
||
private final @NotNull Process process; | ||
private final @NotNull InputStream inputStream; | ||
private final @NotNull OutputStream outputStream; | ||
|
||
public FfmpegAudioWriter( | ||
@NotNull String ffmpegCommand, | ||
@NotNull File outputFile, | ||
@NotNull String outputFormat, | ||
int sampleRate, | ||
int channels | ||
) { | ||
List<String> commandArguments = new ArrayList<>(); | ||
commandArguments.add("-y"); | ||
commandArguments.add("-f s16le"); | ||
commandArguments.add("-ar " + sampleRate); | ||
commandArguments.add("-ac " + channels); | ||
commandArguments.add("-i -"); | ||
commandArguments.add("-c:a " + outputFormat); | ||
commandArguments.add(outputFile.getName()); | ||
|
||
String[] commandLine = (new CommandLine(ffmpegCommand)) | ||
.addArguments(String.join(" ", commandArguments), false) | ||
.toStrings(); | ||
|
||
ReplayVoiceAddon.LOGGER.info("Starting ffmpeg process: {}", String.join(" ", commandLine)); | ||
|
||
try { | ||
process = (new ProcessBuilder(commandLine)) | ||
.directory(outputFile.getParentFile()) | ||
.redirectErrorStream(true) | ||
.start(); | ||
|
||
inputStream = process.getInputStream(); | ||
outputStream = process.getOutputStream(); | ||
} catch (IOException e) { | ||
ReplayVoiceAddon.LOGGER.info("Failed to create ffmpeg process", e); | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public void write(byte[] samples) { | ||
try { | ||
outputStream.write(samples); | ||
|
||
byte[] available = new byte[inputStream.available()]; | ||
inputStream.read(available); | ||
} catch (IOException e) { | ||
ReplayVoiceAddon.LOGGER.info("Failed to write to ffmpeg stdin", e); | ||
} | ||
} | ||
|
||
@Override | ||
public void close() throws Exception { | ||
try { | ||
outputStream.flush(); | ||
outputStream.close(); | ||
inputStream.close(); | ||
process.waitFor(); | ||
} catch (InterruptedException | IOException e) { | ||
ReplayVoiceAddon.LOGGER.info("Failed to exit ffmpeg process", e); | ||
} | ||
process.destroy(); | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
versions/src/main/java/su/plo/replayvoice/render/LoopbackAudioRender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package su.plo.replayvoice.render; | ||
|
||
import com.replaymod.render.rendering.VideoRenderer; | ||
import lombok.RequiredArgsConstructor; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.lwjgl.openal.SOFTLoopback; | ||
import su.plo.voice.api.client.PlasmoVoiceClient; | ||
import su.plo.voice.api.client.audio.device.AlContextOutputDevice; | ||
import su.plo.voice.api.client.connection.ServerInfo; | ||
import su.plo.voice.api.util.AudioUtil; | ||
|
||
import java.io.File; | ||
import java.util.Arrays; | ||
|
||
@RequiredArgsConstructor | ||
public class LoopbackAudioRender implements AutoCloseable { | ||
|
||
private final @NotNull PlasmoVoiceClient voiceClient; | ||
private final @NotNull VideoRenderer videoRenderer; | ||
|
||
private FfmpegAudioWriter writer; | ||
private int frameSize; | ||
private short[] shortsBuffer; | ||
|
||
private boolean initialized = false; | ||
|
||
public synchronized void render() { | ||
if (!initialized) { | ||
initializeWriter(); | ||
return; | ||
} | ||
|
||
AlContextOutputDevice outputDevice = voiceClient.getDeviceManager() | ||
.getOutputDevice() | ||
.orElse(null); | ||
if (outputDevice == null) return; | ||
|
||
long devicePointer = outputDevice.getDevicePointer(); | ||
|
||
outputDevice.runInContextBlocking(() -> { | ||
SOFTLoopback.alcRenderSamplesSOFT(devicePointer, shortsBuffer, frameSize); | ||
writer.write(AudioUtil.shortsToBytes(shortsBuffer)); | ||
}); | ||
} | ||
|
||
@Override | ||
public synchronized void close() throws Exception { | ||
if (!initialized) return; | ||
|
||
writer.close(); | ||
} | ||
|
||
private void initializeWriter() { | ||
ServerInfo serverInfo = voiceClient.getServerInfo().orElse(null); | ||
if (serverInfo == null) return; | ||
|
||
VoiceAudioRender.reloadDevice(); | ||
|
||
File outputVideoFile = videoRenderer.getRenderSettings().getOutputFile(); | ||
File outputFolder = outputVideoFile.getParentFile(); | ||
String[] outputFileNameSplit = outputVideoFile.getName().split("\\."); | ||
String outputFileName = String.join(".", Arrays.copyOf(outputFileNameSplit, outputFileNameSplit.length - 1)) + "-voice.aac"; | ||
String ffmpegCommand = videoRenderer.getRenderSettings().getExportCommandOrDefault(); | ||
|
||
int sampleRate = serverInfo.getVoiceInfo().getCaptureInfo().getSampleRate(); | ||
int fps = videoRenderer.getRenderSettings().getFramesPerSecond(); | ||
this.frameSize = sampleRate / fps; | ||
int channels = 2; | ||
this.shortsBuffer = new short[frameSize * channels]; | ||
|
||
this.writer = new FfmpegAudioWriter( | ||
ffmpegCommand, | ||
new File(outputFolder, outputFileName), | ||
"aac", | ||
sampleRate, | ||
channels | ||
); | ||
this.initialized = true; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
versions/src/main/java/su/plo/replayvoice/render/VoiceAudioRender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package su.plo.replayvoice.render; | ||
|
||
import com.replaymod.render.rendering.VideoRenderer; | ||
import org.jetbrains.annotations.NotNull; | ||
import su.plo.replayvoice.ReplayVoiceAddon; | ||
import su.plo.voice.api.client.audio.device.DeviceException; | ||
import su.plo.voice.api.client.audio.device.DeviceManager; | ||
|
||
public final class VoiceAudioRender { | ||
|
||
public static LoopbackAudioRender AUDIO_RENDER; | ||
|
||
public static boolean isRendering() { | ||
return AUDIO_RENDER != null; | ||
} | ||
|
||
public static void startRender(@NotNull VideoRenderer renderer) { | ||
if (AUDIO_RENDER != null) return; | ||
|
||
AUDIO_RENDER = new LoopbackAudioRender(ReplayVoiceAddon.INSTANCE.voiceClient, renderer); | ||
} | ||
|
||
public static void stopRender(@NotNull VideoRenderer videoRenderer) { | ||
if (AUDIO_RENDER == null) return; | ||
|
||
try { | ||
AUDIO_RENDER.close(); | ||
} catch (Exception e) { | ||
ReplayVoiceAddon.LOGGER.error("Failed to close audio renderer", e); | ||
} | ||
AUDIO_RENDER = null; | ||
|
||
reloadDevice(); | ||
} | ||
|
||
public static void reloadDevice() { | ||
DeviceManager devices = ReplayVoiceAddon.INSTANCE.voiceClient.getDeviceManager(); | ||
|
||
devices.getOutputDevice().ifPresent((device) -> { | ||
try { | ||
device.reload(); | ||
} catch (DeviceException e) { | ||
ReplayVoiceAddon.LOGGER.error("Failed to reload output device", e); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.