use of net.pms.dlna.InputFile in project UniversalMediaServer by UniversalMediaServer.
the class FFMpegVideo method getVideoTranscodeOptions.
/**
* Returns a list of <code>String</code>s representing ffmpeg output
* options (i.e. options that define the output file's video codec,
* audio codec and container) compatible with the renderer's
* <code>TranscodeVideo</code> profile.
*
* @param dlna
* @param media the media metadata for the video being streamed. May contain unset/null values (e.g. for web videos).
* @param params output parameters
*
* @return a {@link List} of <code>String</code>s representing the FFmpeg output parameters for the renderer according
* to its <code>TranscodeVideo</code> profile.
*/
public synchronized List<String> getVideoTranscodeOptions(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) {
List<String> transcodeOptions = new ArrayList<>();
final String filename = dlna.getFileName();
final RendererConfiguration renderer = params.mediaRenderer;
String customFFmpegOptions = renderer.getCustomFFmpegOptions();
if ((renderer.isTranscodeToWMV() && !renderer.isXbox360()) || (renderer.isXboxOne() && purpose() == VIDEO_WEBSTREAM_PLAYER)) {
// WMV
transcodeOptions.add("-c:v");
transcodeOptions.add("wmv2");
if (!customFFmpegOptions.contains("-c:a ")) {
transcodeOptions.add("-c:a");
transcodeOptions.add("wmav2");
}
transcodeOptions.add("-f");
transcodeOptions.add("asf");
} else {
// MPEGPSMPEG2AC3, MPEGTSMPEG2AC3, MPEGTSH264AC3 or MPEGTSH264AAC
final boolean isTsMuxeRVideoEngineEnabled = configuration.getEnginesAsList(PMS.get().getRegistry()).contains(TsMuxeRVideo.ID);
// Output audio codec
dtsRemux = isTsMuxeRVideoEngineEnabled && configuration.isAudioEmbedDtsInPcm() && params.aid != null && params.aid.isDTS() && !avisynth() && renderer.isDTSPlayable();
boolean isSubtitlesAndTimeseek = !isDisableSubtitles(params) && params.timeseek > 0;
if (configuration.isAudioRemuxAC3() && params.aid != null && params.aid.isAC3() && !avisynth() && renderer.isTranscodeToAC3() && !isSubtitlesAndTimeseek) {
// AC-3 remux
if (!customFFmpegOptions.contains("-c:a ")) {
transcodeOptions.add("-c:a");
transcodeOptions.add("copy");
}
} else {
if (dtsRemux) {
// Audio is added in a separate process later
transcodeOptions.add("-an");
} else if (type() == Format.AUDIO) {
// Skip
} else if (renderer.isTranscodeToAAC()) {
transcodeOptions.add("-c:a");
transcodeOptions.add("aac");
} else {
if (!customFFmpegOptions.contains("-c:a ")) {
transcodeOptions.add("-c:a");
transcodeOptions.add("ac3");
}
}
}
InputFile newInput = null;
if (filename != null) {
newInput = new InputFile();
newInput.setFilename(filename);
newInput.setPush(params.stdin);
}
// Output video codec
if (renderer.isTranscodeToH264() || renderer.isTranscodeToH265()) {
if (!customFFmpegOptions.contains("-c:v")) {
transcodeOptions.add("-c:v");
if (renderer.isTranscodeToH264()) {
transcodeOptions.add("libx264");
} else {
transcodeOptions.add("libx265");
}
transcodeOptions.add("-tune");
transcodeOptions.add("zerolatency");
}
if (!customFFmpegOptions.contains("-preset")) {
transcodeOptions.add("-preset");
transcodeOptions.add("ultrafast");
}
if (!customFFmpegOptions.contains("-level")) {
transcodeOptions.add("-level");
transcodeOptions.add("31");
}
transcodeOptions.add("-pix_fmt");
transcodeOptions.add("yuv420p");
} else if (!dtsRemux) {
transcodeOptions.add("-c:v");
transcodeOptions.add("mpeg2video");
}
if (!customFFmpegOptions.contains("-f")) {
// Output file format
transcodeOptions.add("-f");
if (dtsRemux) {
transcodeOptions.add("mpeg2video");
} else if (renderer.isTranscodeToMPEGTS()) {
transcodeOptions.add("mpegts");
} else {
transcodeOptions.add("vob");
}
}
}
return transcodeOptions;
}
use of net.pms.dlna.InputFile in project UniversalMediaServer by UniversalMediaServer.
the class FFMpegVideo method launchTranscode.
@Override
public synchronized ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) throws IOException {
final String filename = dlna.getFileName();
InputFile newInput = new InputFile();
newInput.setFilename(filename);
newInput.setPush(params.stdin);
// Use device-specific pms conf
PmsConfiguration prev = configuration;
configuration = (DeviceConfiguration) params.mediaRenderer;
RendererConfiguration renderer = params.mediaRenderer;
/*
* Check if the video track and the container report different aspect ratios
*/
boolean aspectRatiosMatch = true;
if (media.getAspectRatioContainer() != null && media.getAspectRatioVideoTrack() != null && !media.getAspectRatioContainer().equals(media.getAspectRatioVideoTrack())) {
aspectRatiosMatch = false;
}
/*
* FFmpeg uses multithreading by default, so provided that the
* user has not disabled FFmpeg multithreading and has not
* chosen to use more or less threads than are available, do not
* specify how many cores to use.
*/
int nThreads = 1;
if (configuration.isFfmpegMultithreading()) {
if (Runtime.getRuntime().availableProcessors() == configuration.getNumberOfCpuCores()) {
nThreads = 0;
} else {
nThreads = configuration.getNumberOfCpuCores();
}
}
List<String> cmdList = new ArrayList<>();
boolean avisynth = avisynth();
if (params.timeseek > 0) {
params.waitbeforestart = 1;
} else if (renderer.isTranscodeFastStart()) {
params.manageFastStart();
} else {
params.waitbeforestart = 2500;
}
setAudioAndSubs(filename, media, params);
dlna.setMediaSubtitle(params.sid);
cmdList.add(executable());
// Prevent FFmpeg timeout
cmdList.add("-y");
cmdList.add("-loglevel");
if (LOGGER.isTraceEnabled()) {
// Set -loglevel in accordance with LOGGER setting
// Could be changed to "verbose" or "debug" if "info" level is not enough
cmdList.add("info");
} else {
cmdList.add("fatal");
}
if (params.timeseek > 0) {
cmdList.add("-ss");
cmdList.add(String.valueOf(params.timeseek));
}
// Decoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add(String.valueOf(nThreads));
}
final boolean isTsMuxeRVideoEngineEnabled = configuration.getEnginesAsList(PMS.get().getRegistry()).contains(TsMuxeRVideo.ID);
final boolean isXboxOneWebVideo = params.mediaRenderer.isXboxOne() && purpose() == VIDEO_WEBSTREAM_PLAYER;
ac3Remux = false;
dtsRemux = false;
if (configuration.isAudioRemuxAC3() && params.aid != null && params.aid.isAC3() && !avisynth() && renderer.isTranscodeToAC3() && !isXboxOneWebVideo && params.aid.getAudioProperties().getNumberOfChannels() <= configuration.getAudioChannelCount()) {
// AC-3 remux takes priority
ac3Remux = true;
} else {
// Now check for DTS remux and LPCM streaming
dtsRemux = isTsMuxeRVideoEngineEnabled && configuration.isAudioEmbedDtsInPcm() && params.aid != null && params.aid.isDTS() && !avisynth() && params.mediaRenderer.isDTSPlayable();
}
String frameRateRatio = media.getValidFps(true);
String frameRateNumber = media.getValidFps(false);
// Input filename
cmdList.add("-i");
if (avisynth && !filename.toLowerCase().endsWith(".iso")) {
File avsFile = AviSynthFFmpeg.getAVSScript(filename, params.sid, params.fromFrame, params.toFrame, frameRateRatio, frameRateNumber, configuration);
cmdList.add(ProcessUtil.getShortFileNameIfWideChars(avsFile.getAbsolutePath()));
} else {
if (params.stdin != null) {
cmdList.add("pipe:");
} else {
cmdList.add(filename);
}
}
/**
* Defer to MEncoder for subtitles if:
* - The setting is enabled
* - There are subtitles to transcode
* - The file is not being played via the transcode folder
*/
if (!(renderer instanceof RendererConfiguration.OutputOverride) && params.sid != null && !(configuration.isShowTranscodeFolder() && dlna.isNoName() && (dlna.getParent() instanceof FileTranscodeVirtualFolder)) && configuration.isFFmpegDeferToMEncoderForProblematicSubtitles() && params.sid.isEmbedded() && ((params.sid.getType().isText() && params.sid.getType() != SubtitleType.ASS) || params.sid.getType() == SubtitleType.VOBSUB)) {
LOGGER.trace("Switching from FFmpeg to MEncoder to transcode subtitles because the user setting is enabled.");
MEncoderVideo mv = new MEncoderVideo();
return mv.launchTranscode(dlna, media, params);
}
// Decide whether to defer to tsMuxeR or continue to use FFmpeg
if (!(renderer instanceof RendererConfiguration.OutputOverride) && configuration.isFFmpegMuxWithTsMuxerWhenCompatible()) {
// Decide whether to defer to tsMuxeR or continue to use FFmpeg
boolean deferToTsmuxer = true;
String prependTraceReason = "Not muxing the video stream with tsMuxeR via FFmpeg because ";
if (deferToTsmuxer == true && configuration.isShowTranscodeFolder() && dlna.isNoName() && (dlna.getParent() instanceof FileTranscodeVirtualFolder)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the file is being played via a FFmpeg entry in the transcode folder.");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isMuxH264MpegTS()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the renderer does not support H.264 inside MPEG-TS.");
}
if (deferToTsmuxer == true && params.sid != null) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we need to burn subtitles.");
}
if (deferToTsmuxer == true && avisynth()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we are using AviSynth.");
}
if (deferToTsmuxer == true && params.mediaRenderer.isH264Level41Limited() && !media.isVideoWithinH264LevelLimits(newInput, params.mediaRenderer)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the video stream is not within H.264 level limits for this renderer.");
}
if (deferToTsmuxer == true && !media.isMuxable(params.mediaRenderer)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the video stream is not muxable to this renderer");
}
if (deferToTsmuxer == true && !aspectRatiosMatch) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we need to transcode to apply the correct aspect ratio.");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isPS3() && media.isWebDl(filename, params)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the version of tsMuxeR supported by this renderer does not support WEB-DL files.");
}
if (deferToTsmuxer == true && "bt.601".equals(media.getMatrixCoefficients())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the colorspace probably isn't supported by the renderer.");
}
if (deferToTsmuxer == true && (params.mediaRenderer.isKeepAspectRatio() || params.mediaRenderer.isKeepAspectRatioTranscoding()) && !"16:9".equals(media.getAspectRatioContainer())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the renderer needs us to add borders so it displays the correct aspect ratio of " + media.getAspectRatioContainer() + ".");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the resolution is incompatible with the renderer.");
}
if (deferToTsmuxer) {
TsMuxeRVideo tv = new TsMuxeRVideo();
params.forceFps = media.getValidFps(false);
if (media.getCodecV() != null) {
if (media.isH264()) {
params.forceType = "V_MPEG4/ISO/AVC";
} else if (media.getCodecV().startsWith("mpeg2")) {
params.forceType = "V_MPEG-2";
} else if (media.getCodecV().equals("vc1")) {
params.forceType = "V_MS/VFW/WVC1";
}
}
return tv.launchTranscode(dlna, media, params);
}
}
// Apply any video filters and associated options. These should go
// after video input is specified and before output streams are mapped.
cmdList.addAll(getVideoFilterOptions(dlna, media, params));
// Map the output streams if necessary
if (media.getAudioTracksList().size() > 1) {
// Set the video stream
cmdList.add("-map");
cmdList.add("0:v");
// Set the proper audio stream
cmdList.add("-map");
cmdList.add("0:a:" + (media.getAudioTracksList().indexOf(params.aid)));
}
// Encoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add(String.valueOf(nThreads));
}
if (params.timeend > 0) {
cmdList.add("-t");
cmdList.add(String.valueOf(params.timeend));
}
// Add the output options (-f, -c:a, -c:v, etc.)
// Now that inputs and filtering are complete, see if we should
// give the renderer the final say on the command
boolean override = false;
if (renderer instanceof RendererConfiguration.OutputOverride) {
override = ((RendererConfiguration.OutputOverride) renderer).getOutputOptions(cmdList, dlna, this, params);
}
if (!override) {
cmdList.addAll(getVideoBitrateOptions(dlna, media, params));
String customFFmpegOptions = renderer.getCustomFFmpegOptions();
// Audio bitrate
if (!ac3Remux && !dtsRemux && !(type() == Format.AUDIO)) {
int channels = 0;
if ((renderer.isTranscodeToWMV() && !renderer.isXbox360()) || (renderer.isXboxOne() && purpose() == VIDEO_WEBSTREAM_PLAYER)) {
channels = 2;
} else if (params.aid != null && params.aid.getAudioProperties().getNumberOfChannels() > configuration.getAudioChannelCount()) {
channels = configuration.getAudioChannelCount();
}
if (!customFFmpegOptions.contains("-ac ") && channels > 0) {
cmdList.add("-ac");
cmdList.add(String.valueOf(channels));
}
if (!customFFmpegOptions.contains("-ab ")) {
cmdList.add("-ab");
if (renderer.isTranscodeToAAC()) {
cmdList.add(Math.min(configuration.getAudioBitrate(), 320) + "k");
} else {
cmdList.add(String.valueOf(CodecUtil.getAC3Bitrate(configuration, params.aid)) + "k");
}
}
if (!customFFmpegOptions.contains("-ar ")) {
cmdList.add("-ar");
cmdList.add("" + params.mediaRenderer.getTranscodedVideoAudioSampleRate());
}
}
// Add the output options (-f, -c:a, -c:v, etc.)
cmdList.addAll(getVideoTranscodeOptions(dlna, media, params));
// Add custom options
if (StringUtils.isNotEmpty(customFFmpegOptions)) {
parseOptions(customFFmpegOptions, cmdList);
}
}
// Set up the process
PipeProcess pipe = null;
if (!dtsRemux) {
// cmdList.add("pipe:");
// basename of the named pipe:
String fifoName = String.format("ffmpegvideo_%d_%d", Thread.currentThread().getId(), System.currentTimeMillis());
// This process wraps the command that creates the named pipe
pipe = new PipeProcess(fifoName);
// delete the named pipe later; harmless if it isn't created
pipe.deleteLater();
params.input_pipes[0] = pipe;
// Output file
cmdList.add(pipe.getInputPipe());
}
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
cmdArray = finalizeTranscoderArgs(filename, dlna, media, params, cmdArray);
ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);
setOutputParsing(dlna, pw, false);
if (!dtsRemux) {
ProcessWrapper mkfifo_process = pipe.getPipeProcess();
/**
* It can take a long time for Windows to create a named pipe (and
* mkfifo can be slow if /tmp isn't memory-mapped), so run this in
* the current thread.
*/
mkfifo_process.runInSameThread();
// Clean up the mkfifo process when the transcode ends
pw.attachProcess(mkfifo_process);
// Give the mkfifo process a little time
try {
Thread.sleep(300);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted while waiting for named pipe to be created", e);
}
} else {
pipe = new PipeProcess(System.currentTimeMillis() + "tsmuxerout.ts");
TsMuxeRVideo ts = new TsMuxeRVideo();
File f = new File(configuration.getTempFolder(), "pms-tsmuxer.meta");
String[] cmd = new String[] { ts.executable(), f.getAbsolutePath(), pipe.getInputPipe() };
pw = new ProcessWrapperImpl(cmd, params);
PipeIPCProcess ffVideoPipe = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegvideo", System.currentTimeMillis() + "videoout", false, true);
cmdList.add(ffVideoPipe.getInputPipe());
OutputParams ffparams = new OutputParams(configuration);
ffparams.maxBufferSize = 1;
ffparams.stdin = params.stdin;
String[] cmdArrayDts = new String[cmdList.size()];
cmdList.toArray(cmdArrayDts);
cmdArrayDts = finalizeTranscoderArgs(filename, dlna, media, params, cmdArrayDts);
ProcessWrapperImpl ffVideo = new ProcessWrapperImpl(cmdArrayDts, ffparams);
ProcessWrapper ff_video_pipe_process = ffVideoPipe.getPipeProcess();
pw.attachProcess(ff_video_pipe_process);
ff_video_pipe_process.runInNewThread();
ffVideoPipe.deleteLater();
pw.attachProcess(ffVideo);
ffVideo.runInNewThread();
PipeIPCProcess ffAudioPipe = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegaudio01", System.currentTimeMillis() + "audioout", false, true);
StreamModifier sm = new StreamModifier();
sm.setPcm(false);
sm.setDtsEmbed(dtsRemux);
sm.setSampleFrequency(48000);
sm.setBitsPerSample(16);
sm.setNbChannels(2);
List<String> cmdListDTS = new ArrayList<>();
cmdListDTS.add(executable());
cmdListDTS.add("-y");
cmdListDTS.add("-ss");
if (params.timeseek > 0) {
cmdListDTS.add(String.valueOf(params.timeseek));
} else {
cmdListDTS.add("0");
}
if (params.stdin == null) {
cmdListDTS.add("-i");
} else {
cmdListDTS.add("-");
}
cmdListDTS.add(filename);
if (params.timeseek > 0) {
cmdListDTS.add("-copypriorss");
cmdListDTS.add("0");
cmdListDTS.add("-avoid_negative_ts");
cmdListDTS.add("1");
}
cmdListDTS.add("-ac");
cmdListDTS.add("2");
cmdListDTS.add("-f");
cmdListDTS.add("dts");
cmdListDTS.add("-c:a");
cmdListDTS.add("copy");
cmdListDTS.add(ffAudioPipe.getInputPipe());
String[] cmdArrayDTS = new String[cmdListDTS.size()];
cmdListDTS.toArray(cmdArrayDTS);
if (!params.mediaRenderer.isMuxDTSToMpeg()) {
// No need to use the PCM trick when media renderer supports DTS
ffAudioPipe.setModifier(sm);
}
OutputParams ffaudioparams = new OutputParams(configuration);
ffaudioparams.maxBufferSize = 1;
ffaudioparams.stdin = params.stdin;
ProcessWrapperImpl ffAudio = new ProcessWrapperImpl(cmdArrayDTS, ffaudioparams);
params.stdin = null;
try (PrintWriter pwMux = new PrintWriter(f)) {
pwMux.println("MUXOPT --no-pcr-on-video-pid --no-asyncio --new-audio-pes --vbr --vbv-len=500");
String videoType = "V_MPEG-2";
if (renderer.isTranscodeToH264()) {
videoType = "V_MPEG4/ISO/AVC";
}
if (params.no_videoencode && params.forceType != null) {
videoType = params.forceType;
}
StringBuilder fps = new StringBuilder();
fps.append("");
if (params.forceFps != null) {
fps.append("fps=").append(params.forceFps).append(", ");
}
String audioType = "A_AC3";
if (dtsRemux) {
if (params.mediaRenderer.isMuxDTSToMpeg()) {
// Renderer can play proper DTS track
audioType = "A_DTS";
} else {
// DTS padded in LPCM trick
audioType = "A_LPCM";
}
}
pwMux.println(videoType + ", \"" + ffVideoPipe.getOutputPipe() + "\", " + fps + "level=4.1, insertSEI, contSPS, track=1");
pwMux.println(audioType + ", \"" + ffAudioPipe.getOutputPipe() + "\", track=2");
}
ProcessWrapper pipe_process = pipe.getPipeProcess();
pw.attachProcess(pipe_process);
pipe_process.runInNewThread();
try {
wait(50);
} catch (InterruptedException e) {
}
pipe.deleteLater();
params.input_pipes[0] = pipe;
ProcessWrapper ff_pipe_process = ffAudioPipe.getPipeProcess();
pw.attachProcess(ff_pipe_process);
ff_pipe_process.runInNewThread();
try {
wait(50);
} catch (InterruptedException e) {
}
ffAudioPipe.deleteLater();
pw.attachProcess(ffAudio);
ffAudio.runInNewThread();
}
// Launch the transcode command...
pw.runInNewThread();
// ...and wait briefly to allow it to start
try {
Thread.sleep(200);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted while waiting for transcode to start", e.getMessage());
LOGGER.trace("", e);
}
configuration = prev;
return pw;
}
Aggregations