nThreads = configuration.getNumberOfCpuCores();
}
}
List<String> cmdList = new ArrayList<>();
RendererConfiguration renderer = params.mediaRenderer;
boolean avisynth = avisynth();
if (params.timeseek > 0) {
params.waitbeforestart = 200;
} else {
params.waitbeforestart = 2500;
}
setAudioAndSubs(filename, media, params);
cmdList.add(executable());
// Prevent FFmpeg timeout
cmdList.add("-y");
cmdList.add("-loglevel");
if (LOGGER.isTraceEnabled()) { // Set -loglevel in accordance with LOGGER setting
cmdList.add("verbose"); // Could be changed to "verbose" or "debug" if "info" level is not enough
} else {
cmdList.add("fatal");
}
if (params.timeseek > 0) {
cmdList.add("-ss");
cmdList.add(String.valueOf((int) 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
) {
// 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);
cmdList.add(ProcessUtil.getShortFileNameIfWideChars(avsFile.getAbsolutePath()));
} else {
cmdList.add(filename);
}
/**
* Defer to MEncoder for subtitles if:
* - The setting is enabled or embedded fonts exist
* - There are subtitles to transcode
* - The file is not being played via the transcode folder
*/
String prependTraceReason = "Switching from FFmpeg to MEncoder to transcode subtitles because ";
if (
params.sid != null &&
!(
!configuration.getHideTranscodeEnabled() &&
dlna.isNoName() &&
(dlna.getParent() instanceof FileTranscodeVirtualFolder)
)
) {
boolean deferToMencoder = false;
if (configuration.isFFmpegDeferToMEncoderForSubtitles()) {
deferToMencoder = true;
LOGGER.trace(prependTraceReason + "the user setting is enabled.");
} else if (media.isEmbeddedFontExists()) {
deferToMencoder = true;
LOGGER.trace(prependTraceReason + "there are embedded fonts.");
}
if (deferToMencoder) {
MEncoderVideo mv = new MEncoderVideo();
return mv.launchTranscode(dlna, media, params);
}
}
// Decide whether to defer to tsMuxeR or continue to use FFmpeg
boolean deferToTsmuxer = true;
prependTraceReason = "Not muxing the video stream with tsMuxeR via FFmpeg because ";
if (!configuration.isFFmpegMuxWithTsMuxerWhenCompatible()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the user setting is disabled");
}
if (deferToTsmuxer == true && !configuration.getHideTranscodeEnabled() && 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() && filename.contains("WEB-DL")) {
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() && !"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) {
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)));
}
// Now configure the output streams
// 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));
}
cmdList.addAll(getVideoBitrateOptions(dlna, media, params));
// add audio bitrate options
// TODO: Integrate our (more comprehensive) code with this function
// from PMS to make keeping synchronised easier.
// Until then, leave the following line commented out.
// cmdList.addAll(getAudioBitrateOptions(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");
}
}
}
// 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);
}
if (!dtsRemux) {
cmdList.add("pipe:");
}
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) {
PipeProcess pipe;
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;