Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Added support for input devices/streams and tests #178

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public abstract class AbstractFFmpegStreamBuilder<T extends AbstractFFmpegStream
/** Output filename or uri. Only one may be set */
public String filename;

/** input filename or uri. Only one may be set */
public String input;

public URI uri;

public String format;
Expand Down Expand Up @@ -110,6 +113,11 @@ protected AbstractFFmpegStreamBuilder() {
this.parent = null;
}

//used by input builders
protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent){
this.parent = parent;
}

protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent, String filename) {
this.parent = checkNotNull(parent);
this.filename = checkNotEmpty(filename, "filename must not be empty");
Expand Down
20 changes: 18 additions & 2 deletions src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public String toString() {
Long startOffset; // in millis
boolean read_at_native_frame_rate = false;
final List<String> inputs = new ArrayList<>();
final List<FFmpegInputBuilder> inputStreams = new ArrayList<>();
final Map<String, FFmpegProbeResult> inputProbes = new TreeMap<>();

final List<String> extra_args = new ArrayList<>();
Expand Down Expand Up @@ -241,6 +242,12 @@ public FFmpegOutputBuilder addOutput(URI uri) {
return output;
}

public FFmpegInputBuilder addInputStream(String device){
FFmpegInputBuilder input = new FFmpegInputBuilder(this, device);
inputStreams.add(input);
return input;
}

/**
* Adds an existing FFmpegOutputBuilder. This is similar to calling the other addOuput methods but
* instead allows an existing FFmpegOutputBuilder to be used, and reused.
Expand Down Expand Up @@ -275,7 +282,9 @@ public FFmpegOutputBuilder addStdoutOutput() {
public List<String> build() {
ImmutableList.Builder<String> args = new ImmutableList.Builder<String>();

Preconditions.checkArgument(!inputs.isEmpty(), "At least one input must be specified");
Preconditions.checkArgument(!inputs.isEmpty() || !inputStreams.isEmpty(), "At least one input must be specified");
Preconditions.checkArgument(!(!inputs.isEmpty() && !inputStreams.isEmpty()),
"Cannot specify both an input file/URI and an input device");
Preconditions.checkArgument(!outputs.isEmpty(), "At least one output must be specified");

args.add(override ? "-y" : "-n");
Expand All @@ -285,6 +294,12 @@ public List<String> build() {
args.add("-user_agent", user_agent);
}

if(inputs.isEmpty()){ //build the input streams, using the AbstractFFmpegStreamBuilder
for(FFmpegInputBuilder inputStream: inputStreams){
args.addAll(inputStream.build(this, pass));
}
}

if (startOffset != null) {
args.add("-ss", FFmpegUtils.toTimecode(startOffset, TimeUnit.MILLISECONDS));
}
Expand All @@ -301,7 +316,7 @@ public List<String> build() {
args.add("-progress", progress.toString());
}

args.addAll(extra_args);
if(!inputs.isEmpty()) args.addAll(extra_args);

for (String input : inputs) {
args.add("-i", input);
Expand Down Expand Up @@ -333,4 +348,5 @@ public List<String> build() {

return args.build();
}

}
100 changes: 100 additions & 0 deletions src/main/java/net/bramp/ffmpeg/builder/FFmpegInputBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package net.bramp.ffmpeg.builder;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import net.bramp.ffmpeg.options.EncodingOptions;
import org.apache.commons.lang3.SystemUtils;

import java.util.ArrayList;
import java.util.List;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static net.bramp.ffmpeg.Preconditions.checkNotEmpty;

public class FFmpegInputBuilder extends AbstractFFmpegStreamBuilder<FFmpegInputBuilder> {

protected int thread_queue_size;
protected int device_number; //device number for multiple devices with the same name
protected static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null";

public FFmpegInputBuilder(FFmpegBuilder parent, String source) {
super(parent);
device_number = 0;
this.input = source;
}

public FFmpegInputBuilder(FFmpegBuilder parent, String source, int device_number) {
this(parent, source);
this.device_number = device_number;
}

@Override
protected FFmpegInputBuilder getThis() {
return this;
}

//TO-DO: something here???
@Override
public EncodingOptions buildOptions() {
return null;
}

public FFmpegInputBuilder setThreadQueueSize(int thread_queue_size){
this.thread_queue_size = thread_queue_size;
this.addExtraArgs("-thread_queue_size", String.valueOf(thread_queue_size));
return this;
}

@Override
protected List<String> build(FFmpegBuilder parent, int pass) {
checkNotNull(parent);

if (pass > 0) {
// TODO Write a test for this:
checkArgument(format != null, "Format must be specified when using two-pass");
}

ImmutableList.Builder<String> args = new ImmutableList.Builder<>();

addGlobalFlags(parent, args);

if (video_enabled) {
addVideoFlags(parent, args);
} else {
args.add("-vn");
}

if (audio_enabled && pass != 1) {
addAudioFlags(args);
} else {
args.add("-an");
}

if (subtitle_enabled) {
if (!Strings.isNullOrEmpty(subtitle_preset)) {
args.add("-spre", subtitle_preset);
}
} else {
args.add("-sn");
}

args.addAll(extra_args);

if (filename != null && uri != null) {
throw new IllegalStateException("Only one of filename and uri can be set");
}

// Input
if (pass == 1) {
args.add(DEVNULL);
} else if (input != null) {
args.add("-video_device_number", "" + device_number);
args.add("-i", input);
} else {
assert (false);
}

return args.build();
}
}
37 changes: 37 additions & 0 deletions src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -432,4 +433,40 @@ public void testPresets() {
"-y", "-v", "error", "-i", "input", "-preset", "a", "-fpre", "b", "-vpre", "c", "-apre",
"d", "-spre", "e", "output"));
}

@Test
public void testFFmpegInputBuilder(){
String cmd = "-y -v info -f x11grab -s 1280x720 -r 30/1 -draw_mouse 0 -thread_queue_size 4096 -video_device_number 0 " +
"-i :0.0+0,0 -f alsa -thread_queue_size 4096 -video_device_number 0 -i hw:0,1,0 -f flv -acodec aac rtmp://a.rtmp.youtube.com/live2/XXX" ;
List<String> args =
new FFmpegBuilder()
.overrideOutputFiles(true)
.setVerbosity(FFmpegBuilder.Verbosity.INFO)
.addInputStream(":0.0+0,0")
.setFormat("x11grab")
.addExtraArgs("-draw_mouse", "0")
.setVideoFrameRate(30)
.setVideoResolution("1280x720")
.setThreadQueueSize(4096)
.done()
.addInputStream("hw:0,1,0")
.setFormat("alsa")
.setThreadQueueSize(4096)
.done()
.addOutput("rtmp://a.rtmp.youtube.com/live2/XXX")
.setAudioCodec("aac")
.setFormat("flv")
.done()
.build();

assertEquals(
ImmutableList.copyOf(parseCmd(cmd)),
args
);
}

//convenience method to parse commands into the immutable list assert form
private List<String> parseCmd(String cmd){
return Arrays.asList(cmd.split(" "));
}
}