kopia lustrzana https://github.com/jameshball/osci-render
Add H265, VP9, and ProRes codecs for recording
rodzic
c205de10b7
commit
6a1306ae15
|
@ -62,7 +62,7 @@ public:
|
|||
|
||||
VisualiserSettings visualiserSettings = VisualiserSettings(audioProcessor.visualiserParameters, 3);
|
||||
RecordingSettings recordingSettings = RecordingSettings(audioProcessor.recordingParameters);
|
||||
SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings", recordingSettings, 330, 320, 330, 320);
|
||||
SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings", recordingSettings, 330, 350, 330, 350);
|
||||
VisualiserComponent visualiser{
|
||||
audioProcessor,
|
||||
#if SOSCI_FEATURES
|
||||
|
|
|
@ -13,6 +13,8 @@ RecordingSettings::RecordingSettings(RecordingParameters& ps) : parameters(ps) {
|
|||
addAndMakeVisible(recordVideo);
|
||||
addAndMakeVisible(compressionPreset);
|
||||
addAndMakeVisible(compressionPresetLabel);
|
||||
addAndMakeVisible(videoCodecSelector);
|
||||
addAndMakeVisible(videoCodecLabel);
|
||||
addAndMakeVisible(customSharedTextureOutputLabel);
|
||||
addAndMakeVisible(customSharedTextureOutputEditor);
|
||||
|
||||
|
@ -44,6 +46,19 @@ RecordingSettings::RecordingSettings(RecordingParameters& ps) : parameters(ps) {
|
|||
compressionPreset.setSelectedId(parameters.compressionPresets.indexOf(parameters.compressionPreset) + 1);
|
||||
compressionPresetLabel.setTooltip("The compression preset to use when recording video. Slower presets will produce smaller files at the expense of encoding time.");
|
||||
|
||||
// Setup the codec selector
|
||||
videoCodecSelector.addItem("H.264", static_cast<int>(VideoCodec::H264) + 1);
|
||||
videoCodecSelector.addItem("H.265/HEVC", static_cast<int>(VideoCodec::H265) + 1);
|
||||
videoCodecSelector.addItem("VP9", static_cast<int>(VideoCodec::VP9) + 1);
|
||||
#if JUCE_MAC
|
||||
videoCodecSelector.addItem("ProRes", static_cast<int>(VideoCodec::ProRes) + 1);
|
||||
#endif
|
||||
videoCodecSelector.setSelectedId(static_cast<int>(parameters.videoCodec) + 1);
|
||||
videoCodecSelector.onChange = [this] {
|
||||
parameters.videoCodec = static_cast<VideoCodec>(videoCodecSelector.getSelectedId() - 1);
|
||||
};
|
||||
videoCodecLabel.setTooltip("The video codec to use when recording. Different codecs offer different trade-offs between quality, file size, and compatibility.");
|
||||
|
||||
customSharedTextureOutputLabel.setTooltip("Custom name for when creating a new Syphon/Spout server. WARNING: You should not use the same name when running multiple servers at once!.");
|
||||
customSharedTextureOutputEditor.setText(parameters.customSharedTextureServerName);
|
||||
customSharedTextureOutputEditor.onTextChange = [this] {
|
||||
|
@ -77,10 +92,16 @@ void RecordingSettings::resized() {
|
|||
frameRate.setBounds(area.removeFromTop(rowHeight).expanded(6, 0));
|
||||
recordAudio.setBounds(area.removeFromTop(rowHeight));
|
||||
recordVideo.setBounds(area.removeFromTop(rowHeight));
|
||||
|
||||
auto row = area.removeFromTop(rowHeight);
|
||||
compressionPresetLabel.setBounds(row.removeFromLeft(170));
|
||||
compressionPreset.setBounds(row.removeFromRight(100));
|
||||
|
||||
area.removeFromTop(5);
|
||||
row = area.removeFromTop(rowHeight);
|
||||
videoCodecLabel.setBounds(row.removeFromLeft(170));
|
||||
videoCodecSelector.setBounds(row.removeFromRight(100));
|
||||
|
||||
area.removeFromTop(5);
|
||||
row = area.removeFromTop(rowHeight);
|
||||
customSharedTextureOutputLabel.setBounds(row.removeFromLeft(170));
|
||||
|
|
|
@ -8,6 +8,16 @@
|
|||
|
||||
#define VERSION_HINT 2
|
||||
|
||||
// Define codec options
|
||||
enum class VideoCodec {
|
||||
H264,
|
||||
H265,
|
||||
VP9,
|
||||
#if JUCE_MAC
|
||||
ProRes,
|
||||
#endif
|
||||
};
|
||||
|
||||
class RecordingParameters {
|
||||
public:
|
||||
RecordingParameters() {
|
||||
|
@ -58,6 +68,7 @@ public:
|
|||
Effect frameRateEffect = Effect(&frameRate);
|
||||
|
||||
juce::String compressionPreset = "fast";
|
||||
VideoCodec videoCodec = VideoCodec::H264;
|
||||
|
||||
void save(juce::XmlElement* xml) {
|
||||
auto settingsXml = xml->createNewChildElement("recordingSettings");
|
||||
|
@ -66,6 +77,7 @@ public:
|
|||
recordVideo.save(settingsXml->createNewChildElement("recordVideo"));
|
||||
settingsXml->setAttribute("compressionPreset", compressionPreset);
|
||||
settingsXml->setAttribute("customSharedTextureServerName", customSharedTextureServerName);
|
||||
settingsXml->setAttribute("videoCodec", static_cast<int>(videoCodec));
|
||||
|
||||
auto qualityXml = settingsXml->createNewChildElement("quality");
|
||||
qualityEffect.save(qualityXml);
|
||||
|
@ -95,6 +107,10 @@ public:
|
|||
if (settingsXml->hasAttribute("customSharedTextureServerName")) {
|
||||
customSharedTextureServerName = settingsXml->getStringAttribute("customSharedTextureServerName");
|
||||
}
|
||||
if (settingsXml->hasAttribute("videoCodec")) {
|
||||
int codecValue = settingsXml->getIntAttribute("videoCodec", 0);
|
||||
videoCodec = static_cast<VideoCodec>(codecValue);
|
||||
}
|
||||
if (auto* qualityXml = settingsXml->getChildByName("quality")) {
|
||||
qualityEffect.load(qualityXml);
|
||||
}
|
||||
|
@ -163,6 +179,24 @@ public:
|
|||
return parameters.frameRate.getValueUnnormalised();
|
||||
}
|
||||
|
||||
VideoCodec getVideoCodec() {
|
||||
return parameters.videoCodec;
|
||||
}
|
||||
|
||||
juce::String getFileExtensionForCodec() {
|
||||
switch (parameters.videoCodec) {
|
||||
#if JUCE_MAC
|
||||
case VideoCodec::ProRes:
|
||||
return "mov";
|
||||
#endif
|
||||
case VideoCodec::H264:
|
||||
case VideoCodec::H265:
|
||||
case VideoCodec::VP9:
|
||||
default:
|
||||
return "mp4";
|
||||
}
|
||||
}
|
||||
|
||||
RecordingParameters& parameters;
|
||||
|
||||
private:
|
||||
|
@ -182,6 +216,9 @@ private:
|
|||
juce::Label compressionPresetLabel{"Compression Speed", "Compression Speed"};
|
||||
juce::ComboBox compressionPreset;
|
||||
|
||||
juce::Label videoCodecLabel{"Video Codec", "Video Codec"};
|
||||
juce::ComboBox videoCodecSelector;
|
||||
|
||||
juce::Label customSharedTextureOutputLabel{"Custom Syphon/Spout Name", "Custom Syphon/Spout Name"};
|
||||
juce::TextEditor customSharedTextureOutputEditor{"customSharedTextureOutputEditor"};
|
||||
|
||||
|
|
|
@ -425,7 +425,11 @@ void VisualiserComponent::setRecording(bool recording) {
|
|||
record.setToggleState(false, juce::NotificationType::dontSendNotification);
|
||||
return;
|
||||
}
|
||||
tempVideoFile = std::make_unique<juce::TemporaryFile>(".mp4");
|
||||
|
||||
// Get the appropriate file extension based on codec
|
||||
juce::String fileExtension = recordingSettings.getFileExtensionForCodec();
|
||||
tempVideoFile = std::make_unique<juce::TemporaryFile>("." + fileExtension);
|
||||
|
||||
juce::String resolution = std::to_string(renderTexture.width) + "x" + std::to_string(renderTexture.height);
|
||||
juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\"" +
|
||||
" -r " + juce::String(recordingSettings.getFrameRate()) +
|
||||
|
@ -436,18 +440,39 @@ void VisualiserComponent::setRecording(bool recording) {
|
|||
" -threads 4" +
|
||||
" -preset " + recordingSettings.getCompressionPreset() +
|
||||
" -y" +
|
||||
" -pix_fmt yuv420p" +
|
||||
" -crf " + juce::String(recordingSettings.getCRF()) +
|
||||
#if JUCE_MAC
|
||||
#if JUCE_ARM
|
||||
// use software encoding on Apple Silicon
|
||||
" -c:v hevc_videotoolbox" +
|
||||
" -q:v " + juce::String(recordingSettings.getVideoToolboxQuality()) +
|
||||
" -tag:v hvc1" +
|
||||
#endif
|
||||
" -pix_fmt yuv420p";
|
||||
|
||||
// Apply codec-specific parameters
|
||||
VideoCodec codec = recordingSettings.getVideoCodec();
|
||||
if (codec == VideoCodec::H264) {
|
||||
cmd += " -c:v libx264";
|
||||
cmd += " -crf " + juce::String(recordingSettings.getCRF());
|
||||
}
|
||||
else if (codec == VideoCodec::H265) {
|
||||
cmd += " -c:v libx265";
|
||||
cmd += " -crf " + juce::String(recordingSettings.getCRF());
|
||||
#if JUCE_MAC && JUCE_ARM
|
||||
// use hardware encoding on Apple Silicon
|
||||
cmd += " -c:v hevc_videotoolbox";
|
||||
cmd += " -q:v " + juce::String(recordingSettings.getVideoToolboxQuality());
|
||||
cmd += " -tag:v hvc1";
|
||||
#endif
|
||||
" -vf vflip" +
|
||||
" \"" + tempVideoFile->getFile().getFullPathName() + "\"";
|
||||
}
|
||||
else if (codec == VideoCodec::VP9) {
|
||||
cmd += " -c:v libvpx-vp9";
|
||||
cmd += " -b:v 0";
|
||||
cmd += " -crf " + juce::String(recordingSettings.getCRF());
|
||||
cmd += " -deadline good -cpu-used 2";
|
||||
}
|
||||
#if JUCE_MAC
|
||||
else if (codec == VideoCodec::ProRes) {
|
||||
cmd += " -c:v prores";
|
||||
cmd += " -profile:v 3"; // ProRes 422 HQ
|
||||
}
|
||||
#endif
|
||||
|
||||
cmd += " -vf vflip";
|
||||
cmd += " \"" + tempVideoFile->getFile().getFullPathName() + "\"";
|
||||
|
||||
ffmpegProcess.start(cmd);
|
||||
framePixels.resize(renderTexture.width * renderTexture.height * 4);
|
||||
|
@ -472,7 +497,7 @@ void VisualiserComponent::setRecording(bool recording) {
|
|||
recordingAudio = false;
|
||||
recordingVideo = false;
|
||||
|
||||
juce::String extension = wasRecordingVideo ? "mp4" : "wav";
|
||||
juce::String extension = wasRecordingVideo ? recordingSettings.getFileExtensionForCodec() : "wav";
|
||||
if (wasRecordingAudio) {
|
||||
audioRecorder.stop();
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue