net.pms.encoders.TsMuxeRVideo.java Source code

Java tutorial

Introduction

Here is the source code for net.pms.encoders.TsMuxeRVideo.java

Source

/*
 * PS3 Media Server, for streaming any medias to your PS3.
 * Copyright (C) 2008  A.Brochard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.encoders;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import net.pms.Messages;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.*;
import net.pms.formats.Format;
import net.pms.io.*;
import net.pms.util.CodecUtil;
import net.pms.util.FormLayoutUtil;
import net.pms.util.PlayerUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.*;
import java.net.URL;
import java.util.Locale;

import static net.pms.formats.v2.AudioUtils.getLPCMChannelMappingForMencoder;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.startsWith;

public class TsMuxeRVideo extends Player {
    private static final Logger logger = LoggerFactory.getLogger(TsMuxeRVideo.class);
    private static final String COL_SPEC = "left:pref, 0:grow";
    private static final String ROW_SPEC = "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, 0:grow";

    public static final String ID = "tsmuxer";
    private PmsConfiguration configuration;

    public TsMuxeRVideo(PmsConfiguration configuration) {
        this.configuration = configuration;
    }

    @Override
    public boolean excludeFormat(Format format) {
        String extension = format.getMatchedExtension();
        return extension != null && !extension.equals("mp4") && !extension.equals("mkv") && !extension.equals("ts")
                && !extension.equals("tp") && !extension.equals("m2ts") && !extension.equals("m2t")
                && !extension.equals("mpg") && !extension.equals("evo") && !extension.equals("mpeg")
                && !extension.equals("vob") && !extension.equals("m2v") && !extension.equals("mts")
                && !extension.equals("mov");
    }

    @Override
    public PlayerPurpose getPurpose() {
        return PlayerPurpose.VIDEO_FILE_PLAYER;
    }

    @Override
    public String id() {
        return ID;
    }

    @Override
    public boolean isTimeSeekable() {
        return true;
    }

    @Override
    public String[] args() {
        return null;
    }

    @Override
    public String executable() {
        return configuration.getTsmuxerPath();
    }

    @Override
    public ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params)
            throws IOException {
        final String filename = dlna.getSystemName();
        setAudioAndSubs(filename, media, params, configuration);

        PipeIPCProcess ffVideoPipe;
        ProcessWrapperImpl ffVideo;

        PipeIPCProcess ffAudioPipe[] = null;
        ProcessWrapperImpl ffAudio[] = null;

        String fps = media.getValidFps(false);

        int width = media.getWidth();
        int height = media.getHeight();
        if (width < 320 || height < 240) {
            width = -1;
            height = -1;
        }

        String videoType = "V_MPEG4/ISO/AVC";
        if (startsWith(media.getCodecV(), "mpeg2")) {
            videoType = "V_MPEG-2";
        }

        if (this instanceof TsMuxeRAudio && media.getFirstAudioTrack() != null) {
            String fakeFileName = writeResourceToFile("/resources/images/fake.jpg");
            ffVideoPipe = new PipeIPCProcess(System.currentTimeMillis() + "fakevideo",
                    System.currentTimeMillis() + "videoout", false, true);

            String timeEndValue1 = "-t";
            String timeEndValue2 = "" + params.timeend;
            if (params.timeend < 1) {
                timeEndValue1 = "-y";
                timeEndValue2 = "-y";
            }

            String[] ffmpegLPCMextract = new String[] { configuration.getFfmpegPath(), timeEndValue1, timeEndValue2,
                    "-loop", "1", "-i", fakeFileName, "-qcomp", "0.6", "-qmin", "10", "-qmax", "51", "-qdiff", "4",
                    "-me_range", "4", "-f", "h264", "-vcodec", "libx264", "-an", "-y", ffVideoPipe.getInputPipe() };

            videoType = "V_MPEG4/ISO/AVC";

            OutputParams ffparams = new OutputParams(configuration);
            ffparams.maxBufferSize = 1;
            ffVideo = new ProcessWrapperImpl(ffmpegLPCMextract, ffparams);

            if (filename.toLowerCase().endsWith(".flac") && media.getFirstAudioTrack().getBitsperSample() >= 24
                    && media.getFirstAudioTrack().getSampleRate() % 48000 == 0) {
                ffAudioPipe = new PipeIPCProcess[1];
                ffAudioPipe[0] = new PipeIPCProcess(System.currentTimeMillis() + "flacaudio",
                        System.currentTimeMillis() + "audioout", false, true);

                String[] flacCmd = new String[] { configuration.getFlacPath(),
                        "--output-name=" + ffAudioPipe[0].getInputPipe(), "-d", "-f", "-F", filename };

                ffparams = new OutputParams(configuration);
                ffparams.maxBufferSize = 1;
                ffAudio = new ProcessWrapperImpl[1];
                ffAudio[0] = new ProcessWrapperImpl(flacCmd, ffparams);
            } else {
                ffAudioPipe = new PipeIPCProcess[1];
                ffAudioPipe[0] = new PipeIPCProcess(System.currentTimeMillis() + "mlpaudio",
                        System.currentTimeMillis() + "audioout", false, true);
                String depth = "pcm_s16le";
                String rate = "48000";

                if (media.getFirstAudioTrack().getBitsperSample() >= 24) {
                    depth = "pcm_s24le";
                }

                if (media.getFirstAudioTrack().getSampleRate() > 48000) {
                    rate = "" + media.getFirstAudioTrack().getSampleRate();
                }

                String[] flacCmd = new String[] { configuration.getFfmpegPath(), "-i", filename, "-ar", rate, "-f",
                        "wav", "-acodec", depth, "-y", ffAudioPipe[0].getInputPipe() };

                ffparams = new OutputParams(configuration);
                ffparams.maxBufferSize = 1;
                ffAudio = new ProcessWrapperImpl[1];
                ffAudio[0] = new ProcessWrapperImpl(flacCmd, ffparams);
            }
        } else {
            params.waitbeforestart = 5000;
            params.manageFastStart();

            String mencoderPath = configuration.getMencoderPath();

            ffVideoPipe = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegvideo",
                    System.currentTimeMillis() + "videoout", false, true);

            // Special handling for evo files
            String evoValue1 = "-quiet";
            String evoValue2 = "-quiet";
            if (filename.toLowerCase().endsWith(".evo")) {
                evoValue1 = "-psprobe";
                evoValue2 = "1000000";
            }

            String[] ffmpegLPCMextract = new String[] { mencoderPath, "-ss",
                    params.timeseek > 0 ? "" + params.timeseek : "0", params.stdin != null ? "-" : filename,
                    evoValue1, evoValue2, "-really-quiet", "-msglevel", "statusline=2", "-ovc", "copy", "-nosound",
                    "-mc", "0", "-noskip", "-of", "rawvideo", "-o", ffVideoPipe.getInputPipe() };

            InputFile newInput = new InputFile();
            newInput.setFilename(filename);
            newInput.setPush(params.stdin);

            if (media != null) {
                // TODO useful only for logging
                final boolean compat = media.isMediaparsed()
                        && ((newInput != null && media.isVideoWithinH264LevelLimits(newInput, params.mediaRenderer))
                                || !params.mediaRenderer.isH264Level41Limited());

                if (!compat && params.mediaRenderer.isPS3()) {
                    logger.info("The video will not play or will show a black screen on the PS3...");
                }

                if (media.getH264AnnexB() != null && media.getH264AnnexB().length > 0) {
                    StreamModifier sm = new StreamModifier();
                    sm.setHeader(media.getH264AnnexB());
                    sm.setH264AnnexB(true);
                    ffVideoPipe.setModifier(sm);
                }
            }

            OutputParams ffparams = new OutputParams(configuration);
            ffparams.maxBufferSize = 1;
            ffparams.stdin = params.stdin;
            ffVideo = new ProcessWrapperImpl(ffmpegLPCMextract, ffparams);

            int numAudioTracks = 1;

            if (media.getAudioTracksList() != null && media.getAudioTracksList().size() > 1
                    && configuration.isMuxAllAudioTracks()) {
                numAudioTracks = media.getAudioTracksList().size();
            }

            boolean singleMediaAudio = media.getAudioTracksList().size() <= 1;

            if (params.aid != null) {
                boolean ac3Remux;
                boolean dtsRemux;
                boolean pcm;

                // Disable LPCM transcoding for MP4 container with non-H264 video as workaround for MEncoder's A/V sync bug
                boolean mp4_with_non_h264 = (media.getContainer().equals("mp4")
                        && !media.getCodecV().equals("h264"));

                if (numAudioTracks <= 1) {
                    ffAudioPipe = new PipeIPCProcess[numAudioTracks];
                    ffAudioPipe[0] = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegaudio01",
                            System.currentTimeMillis() + "audioout", false, true);

                    /*
                     Disable AC-3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
                     TODO check new firmwares
                     Commented out until we can find a way to detect when a video has an audio track that switches from 2 to 6 channels
                     because MEncoder can't handle those files, which are very common these days.
                    */
                    // final boolean ps3_and_stereo_and_384_kbits = (params.mediaRenderer.isPS3() && params.aid.getAudioProperties().getNumberOfChannels() == 2)
                    //   && (params.aid.getBitRate() > 370000 && params.aid.getBitRate() < 400000);
                    final boolean ps3_and_stereo_and_384_kbits = false;
                    ac3Remux = (params.aid.isAC3() && !ps3_and_stereo_and_384_kbits
                            && configuration.isAudioRemuxAC3());
                    dtsRemux = configuration.isAudioEmbedDtsInPcm() && params.aid.isDTS()
                            && params.mediaRenderer.isDTSPlayable();

                    pcm = configuration.isAudioUsePCM() && !mp4_with_non_h264 && (params.aid.isLossless()
                            || (params.aid.isDTS() && params.aid.getAudioProperties().getNumberOfChannels() <= 6)
                            || params.aid.isTrueHD()
                            || (!configuration.isMencoderUsePcmForHQAudioOnly() && (params.aid.isAC3()
                                    || params.aid.isMP3() || params.aid.isAAC() || params.aid.isVorbis() ||
                                    // params.aid.isWMA() ||
                                    params.aid.isMpegAudio())))
                            && params.mediaRenderer.isLPCMPlayable();

                    int channels;
                    if (ac3Remux) {
                        channels = params.aid.getAudioProperties().getNumberOfChannels(); // AC-3 remux
                    } else if (dtsRemux) {
                        channels = 2;
                    } else if (pcm) {
                        channels = params.aid.getAudioProperties().getNumberOfChannels();
                    } else {
                        channels = configuration.getAudioChannelCount(); // 5.1 max for AC-3 encoding
                    }

                    if (!ac3Remux && (dtsRemux || pcm)) {
                        // DTS remux or LPCM
                        StreamModifier sm = new StreamModifier();
                        sm.setPcm(pcm);
                        sm.setDtsEmbed(dtsRemux);
                        sm.setNbChannels(channels);
                        sm.setSampleFrequency(
                                params.aid.getSampleRate() < 48000 ? 48000 : params.aid.getSampleRate());
                        sm.setBitsPerSample(16);

                        String mixer = null;

                        if (pcm && !dtsRemux) {
                            mixer = getLPCMChannelMappingForMencoder(params.aid);
                        }

                        ffmpegLPCMextract = new String[] { mencoderPath, "-ss",
                                params.timeseek > 0 ? "" + params.timeseek : "0",
                                params.stdin != null ? "-" : filename, evoValue1, evoValue2, "-really-quiet",
                                "-msglevel", "statusline=2", "-channels", "" + sm.getNbChannels(), "-ovc", "copy",
                                "-of", "rawaudio", "-mc", sm.isDtsEmbed() ? "0.1" : "0", "-noskip", "-oac",
                                sm.isDtsEmbed() ? "copy" : "pcm", isNotBlank(mixer) ? "-af" : "-quiet",
                                isNotBlank(mixer) ? mixer : "-quiet", singleMediaAudio ? "-quiet" : "-aid",
                                singleMediaAudio ? "-quiet" : ("" + params.aid.getId()), "-srate", "48000", "-o",
                                ffAudioPipe[0].getInputPipe() };

                        // Use PCM trick when media renderer does not support DTS in MPEG
                        if (!params.mediaRenderer.isMuxDTSToMpeg()) {
                            ffAudioPipe[0].setModifier(sm);
                        }
                    } else {
                        // AC-3 remux or encoding
                        ffmpegLPCMextract = new String[] { mencoderPath, "-ss",
                                params.timeseek > 0 ? "" + params.timeseek : "0",
                                params.stdin != null ? "-" : filename, evoValue1, evoValue2, "-really-quiet",
                                "-msglevel", "statusline=2", "-channels", "" + channels, "-ovc", "copy", "-of",
                                "rawaudio", "-mc", "0", "-noskip", "-oac", (ac3Remux) ? "copy" : "lavc",
                                params.aid.isAC3() ? "-fafmttag" : "-quiet",
                                params.aid.isAC3() ? "0x2000" : "-quiet", "-lavcopts",
                                "acodec=" + (configuration.isMencoderAc3Fixed() ? "ac3_fixed" : "ac3")
                                        + ":abitrate=" + CodecUtil.getAC3Bitrate(configuration, params.aid),
                                "-af", "lavcresample=48000", "-srate", "48000",
                                singleMediaAudio ? "-quiet" : "-aid",
                                singleMediaAudio ? "-quiet" : ("" + params.aid.getId()), "-o",
                                ffAudioPipe[0].getInputPipe() };
                    }

                    ffparams = new OutputParams(configuration);
                    ffparams.maxBufferSize = 1;
                    ffparams.stdin = params.stdin;
                    ffAudio = new ProcessWrapperImpl[numAudioTracks];
                    ffAudio[0] = new ProcessWrapperImpl(ffmpegLPCMextract, ffparams);
                } else {
                    ffAudioPipe = new PipeIPCProcess[numAudioTracks];
                    ffAudio = new ProcessWrapperImpl[numAudioTracks];
                    for (int i = 0; i < media.getAudioTracksList().size(); i++) {
                        DLNAMediaAudio audio = media.getAudioTracksList().get(i);
                        ffAudioPipe[i] = new PipeIPCProcess(System.currentTimeMillis() + "ffmpeg" + i,
                                System.currentTimeMillis() + "audioout" + i, false, true);

                        /*
                         Disable AC-3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
                          TODO check new firmwares
                          Commented out until we can find a way to detect when a video has an audio track that switches from 2 to 6 channels
                          because MEncoder can't handle those files, which are very common these days.
                         */
                        // final boolean ps3_and_stereo_and_384_kbits = (params.mediaRenderer.isPS3() && audio.getAudioProperties().getNumberOfChannels() == 2)
                        //   && (audio.getBitRate() > 370000 && audio.getBitRate() < 400000);
                        final boolean ps3_and_stereo_and_384_kbits = false;
                        ac3Remux = audio.isAC3() && !ps3_and_stereo_and_384_kbits
                                && configuration.isAudioRemuxAC3();
                        dtsRemux = configuration.isAudioEmbedDtsInPcm() && audio.isDTS()
                                && params.mediaRenderer.isDTSPlayable();

                        pcm = configuration.isAudioUsePCM() && !mp4_with_non_h264 && (audio.isLossless()
                                || (audio.isDTS() && audio.getAudioProperties().getNumberOfChannels() <= 6)
                                || audio.isTrueHD() || (!configuration.isMencoderUsePcmForHQAudioOnly()
                                        && (audio.isAC3() || audio.isMP3() || audio.isAAC() || audio.isVorbis() ||
                                        // audio.isWMA() ||
                                                audio.isMpegAudio())))
                                && params.mediaRenderer.isLPCMPlayable();

                        int channels;
                        if (ac3Remux) {
                            channels = audio.getAudioProperties().getNumberOfChannels(); // AC-3 remux
                        } else if (dtsRemux) {
                            channels = 2;
                        } else if (pcm) {
                            channels = audio.getAudioProperties().getNumberOfChannels();
                        } else {
                            channels = configuration.getAudioChannelCount(); // 5.1 max for AC-3 encoding
                        }

                        if (!ac3Remux && (dtsRemux || pcm)) {
                            // DTS remux or LPCM
                            StreamModifier sm = new StreamModifier();
                            sm.setPcm(pcm);
                            sm.setDtsEmbed(dtsRemux);
                            sm.setNbChannels(channels);
                            sm.setSampleFrequency(audio.getSampleRate() < 48000 ? 48000 : audio.getSampleRate());
                            sm.setBitsPerSample(16);
                            if (!params.mediaRenderer.isMuxDTSToMpeg()) {
                                ffAudioPipe[i].setModifier(sm);
                            }

                            String mixer = null;
                            if (pcm && !dtsRemux) {
                                mixer = getLPCMChannelMappingForMencoder(audio);
                            }

                            ffmpegLPCMextract = new String[] { mencoderPath, "-ss",
                                    params.timeseek > 0 ? "" + params.timeseek : "0",
                                    params.stdin != null ? "-" : filename, evoValue1, evoValue2, "-really-quiet",
                                    "-msglevel", "statusline=2", "-channels", "" + sm.getNbChannels(), "-ovc",
                                    "copy", "-of", "rawaudio", "-mc", sm.isDtsEmbed() ? "0.1" : "0", "-noskip",
                                    "-oac", sm.isDtsEmbed() ? "copy" : "pcm", isNotBlank(mixer) ? "-af" : "-quiet",
                                    isNotBlank(mixer) ? mixer : "-quiet", singleMediaAudio ? "-quiet" : "-aid",
                                    singleMediaAudio ? "-quiet" : ("" + audio.getId()), "-srate", "48000", "-o",
                                    ffAudioPipe[i].getInputPipe() };
                        } else {
                            // AC-3 remux or encoding
                            ffmpegLPCMextract = new String[] { mencoderPath, "-ss",
                                    params.timeseek > 0 ? "" + params.timeseek : "0",
                                    params.stdin != null ? "-" : filename, evoValue1, evoValue2, "-really-quiet",
                                    "-msglevel", "statusline=2", "-channels", "" + channels, "-ovc", "copy", "-of",
                                    "rawaudio", "-mc", "0", "-noskip", "-oac", (ac3Remux) ? "copy" : "lavc",
                                    audio.isAC3() ? "-fafmttag" : "-quiet", audio.isAC3() ? "0x2000" : "-quiet",
                                    "-lavcopts",
                                    "acodec=" + (configuration.isMencoderAc3Fixed() ? "ac3_fixed" : "ac3")
                                            + ":abitrate=" + CodecUtil.getAC3Bitrate(configuration, audio),
                                    "-af", "lavcresample=48000", "-srate", "48000",
                                    singleMediaAudio ? "-quiet" : "-aid",
                                    singleMediaAudio ? "-quiet" : ("" + audio.getId()), "-o",
                                    ffAudioPipe[i].getInputPipe() };
                        }

                        ffparams = new OutputParams(configuration);
                        ffparams.maxBufferSize = 1;
                        ffparams.stdin = params.stdin;
                        ffAudio[i] = new ProcessWrapperImpl(ffmpegLPCMextract, ffparams);
                    }
                }
            }
        }

        File f = new File(configuration.getTempFolder(), "pms-tsmuxer.meta");
        params.log = false;
        PrintWriter pw = new PrintWriter(f);
        pw.print("MUXOPT --no-pcr-on-video-pid");
        pw.print(" --new-audio-pes");
        pw.print(" --no-asyncio");
        pw.print(" --vbr");
        pw.println(" --vbv-len=500");

        String videoparams = "level=4.1, insertSEI, contSPS, track=1";
        if (this instanceof TsMuxeRAudio) {
            videoparams = "track=224";
        }
        if (configuration.isFix25FPSAvMismatch()) {
            fps = "25";
        }
        pw.println(videoType + ", \"" + ffVideoPipe.getOutputPipe() + "\", "
                + (fps != null ? ("fps=" + fps + ", ") : "") + (width != -1 ? ("video-width=" + width + ", ") : "")
                + (height != -1 ? ("video-height=" + height + ", ") : "") + videoparams);

        // Disable LPCM transcoding for MP4 container with non-H264 video as workaround for mencoder's A/V sync bug
        boolean mp4_with_non_h264 = (media.getContainer().equals("mp4") && !media.getCodecV().equals("h264"));
        if (ffAudioPipe != null && ffAudioPipe.length == 1) {
            String timeshift = "";
            boolean ac3Remux;
            boolean dtsRemux;
            boolean pcm;
            /*
             Disable AC-3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
             TODO check new firmwares
             Commented out until we can find a way to detect when a video has an audio track that switches from 2 to 6 channels
             because MEncoder can't handle those files, which are very common these days.
            */
            // final boolean ps3_and_stereo_and_384_kbits = (params.mediaRenderer.isPS3() && params.aid.getAudioProperties().getNumberOfChannels() == 2)
            //   && (params.aid.getBitRate() > 370000 && params.aid.getBitRate() < 400000);
            final boolean ps3_and_stereo_and_384_kbits = false;
            ac3Remux = params.aid.isAC3() && !ps3_and_stereo_and_384_kbits && configuration.isAudioRemuxAC3();
            dtsRemux = configuration.isAudioEmbedDtsInPcm() && params.aid.isDTS()
                    && params.mediaRenderer.isDTSPlayable();
            pcm = configuration.isAudioUsePCM() && !mp4_with_non_h264
                    && (params.aid.isLossless()
                            || (params.aid.isDTS() && params.aid.getAudioProperties().getNumberOfChannels() <= 6)
                            || params.aid.isTrueHD()
                            || (!configuration.isMencoderUsePcmForHQAudioOnly() && (params.aid.isAC3()
                                    || params.aid.isMP3() || params.aid.isAAC() || params.aid.isVorbis() ||
                                    // params.aid.isWMA() ||
                                    params.aid.isMpegAudio())))
                    && params.mediaRenderer.isLPCMPlayable();
            String type = "A_AC3";
            if (ac3Remux) {
                // AC-3 remux takes priority
                type = "A_AC3";
            } else {
                if (pcm || this instanceof TsMuxeRAudio) {
                    type = "A_LPCM";
                }
                if (dtsRemux || this instanceof TsMuxeRAudio) {
                    type = "A_LPCM";
                    if (params.mediaRenderer.isMuxDTSToMpeg()) {
                        type = "A_DTS";
                    }
                }
            }
            if (params.aid != null && params.aid.getAudioProperties().getAudioDelay() != 0
                    && params.timeseek == 0) {
                timeshift = "timeshift=" + params.aid.getAudioProperties().getAudioDelay() + "ms, ";
            }
            pw.println(type + ", \"" + ffAudioPipe[0].getOutputPipe() + "\", " + timeshift + "track=2");
        } else if (ffAudioPipe != null) {
            for (int i = 0; i < media.getAudioTracksList().size(); i++) {
                DLNAMediaAudio lang = media.getAudioTracksList().get(i);
                String timeshift = "";
                boolean ac3Remux;
                boolean dtsRemux;
                boolean pcm;
                /*
                 Disable AC-3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
                   TODO check new firmwares
                   Commented out until we can find a way to detect when a video has an audio track that switches from 2 to 6 channels
                   because MEncoder can't handle those files, which are very common these days.
                 */
                // final boolean ps3_and_stereo_and_384_kbits = (params.mediaRenderer.isPS3() && lang.getAudioProperties().getNumberOfChannels() == 2)
                //   && (lang.getBitRate() > 370000 && lang.getBitRate() < 400000);
                final boolean ps3_and_stereo_and_384_kbits = false;
                ac3Remux = lang.isAC3() && !ps3_and_stereo_and_384_kbits && configuration.isAudioRemuxAC3();
                dtsRemux = configuration.isAudioEmbedDtsInPcm() && lang.isDTS()
                        && params.mediaRenderer.isDTSPlayable();
                pcm = configuration.isAudioUsePCM() && !mp4_with_non_h264 && (lang.isLossless()
                        || (lang.isDTS() && lang.getAudioProperties().getNumberOfChannels() <= 6) || lang.isTrueHD()
                        || (!configuration.isMencoderUsePcmForHQAudioOnly() && (params.aid.isAC3()
                                || params.aid.isMP3() || params.aid.isAAC() || params.aid.isVorbis() ||
                                // params.aid.isWMA() ||
                                params.aid.isMpegAudio())))
                        && params.mediaRenderer.isLPCMPlayable();
                String type = "A_AC3";
                if (ac3Remux) {
                    // AC-3 remux takes priority
                    type = "A_AC3";
                } else {
                    if (pcm) {
                        type = "A_LPCM";
                    }
                    if (dtsRemux) {
                        type = "A_LPCM";
                        if (params.mediaRenderer.isMuxDTSToMpeg()) {
                            type = "A_DTS";
                        }
                    }
                }
                if (lang.getAudioProperties().getAudioDelay() != 0 && params.timeseek == 0) {
                    timeshift = "timeshift=" + lang.getAudioProperties().getAudioDelay() + "ms, ";
                }
                pw.println(
                        type + ", \"" + ffAudioPipe[i].getOutputPipe() + "\", " + timeshift + "track=" + (2 + i));
            }
        }

        pw.close();

        PipeProcess tsPipe = new PipeProcess(System.currentTimeMillis() + "tsmuxerout.ts");
        String[] cmdArray = new String[] { executable(), f.getAbsolutePath(), tsPipe.getInputPipe() };

        cmdArray = finalizeTranscoderArgs(filename, dlna, media, params, cmdArray);

        ProcessWrapperImpl p = new ProcessWrapperImpl(cmdArray, params);
        params.maxBufferSize = 100;
        params.input_pipes[0] = tsPipe;
        params.stdin = null;
        ProcessWrapper pipe_process = tsPipe.getPipeProcess();
        p.attachProcess(pipe_process);
        pipe_process.runInNewThread();

        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
        }
        tsPipe.deleteLater();

        ProcessWrapper ff_pipe_process = ffVideoPipe.getPipeProcess();
        p.attachProcess(ff_pipe_process);
        ff_pipe_process.runInNewThread();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
        }
        ffVideoPipe.deleteLater();

        p.attachProcess(ffVideo);
        ffVideo.runInNewThread();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
        }

        if (ffAudioPipe != null && params.aid != null) {
            for (int i = 0; i < ffAudioPipe.length; i++) {
                ff_pipe_process = ffAudioPipe[i].getPipeProcess();
                p.attachProcess(ff_pipe_process);
                ff_pipe_process.runInNewThread();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                }
                ffAudioPipe[i].deleteLater();
                p.attachProcess(ffAudio[i]);
                ffAudio[i].runInNewThread();
            }
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }

        p.runInNewThread();
        return p;
    }

    /**
     * Write the resource "/resources/images/fake.jpg" to a physical file on disk.
     *
     * @return The filename of the file on disk.
     */
    private String writeResourceToFile(String resourceName) {
        String outputFileName = resourceName.substring(resourceName.lastIndexOf("/") + 1);

        try {
            outputFileName = configuration.getTempFolder() + "/" + outputFileName;
        } catch (IOException e) {
            logger.warn("Failure to determine temporary folder.", e);
        }

        File outputFile = new File(outputFileName);

        // Copy the resource file only once
        if (!outputFile.exists()) {
            final URL resourceUrl = getClass().getClassLoader().getResource(resourceName);
            byte[] buffer = new byte[1024];
            int byteCount;

            InputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                inputStream = resourceUrl.openStream();
                outputStream = new FileOutputStream(outputFileName);

                while ((byteCount = inputStream.read(buffer)) >= 0) {
                    outputStream.write(buffer, 0, byteCount);
                }
            } catch (final IOException e) {
                logger.error("Failure on saving the embedded resource " + resourceName + " to the file "
                        + outputFile.getAbsolutePath(), e);
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (final IOException e) {
                        logger.warn("Problem closing an input stream while reading data from the embedded resource "
                                + resourceName, e);
                    }
                }

                if (outputStream != null) {
                    try {
                        outputStream.flush();
                        outputStream.close();
                    } catch (final IOException e) {
                        logger.warn("Problem closing the output stream while writing the file "
                                + outputFile.getAbsolutePath(), e);
                    }
                }
            }
        }

        return outputFileName;
    }

    @Override
    public String mimeType() {
        return "video/mpeg";
    }

    @Override
    public String name() {
        return "tsMuxeR";
    }

    @Override
    public int type() {
        return Format.VIDEO;
    }

    private JCheckBox tsmuxerforcefps;
    private JCheckBox muxallaudiotracks;

    @Override
    public JComponent config() {
        // Apply the orientation for the locale
        Locale locale = new Locale(configuration.getLanguage());
        ComponentOrientation orientation = ComponentOrientation.getOrientation(locale);
        String colSpec = FormLayoutUtil.getColSpec(COL_SPEC, orientation);
        FormLayout layout = new FormLayout(colSpec, ROW_SPEC);

        PanelBuilder builder = new PanelBuilder(layout);
        builder.setBorder(Borders.EMPTY_BORDER);
        builder.setOpaque(false);

        CellConstraints cc = new CellConstraints();

        JComponent cmp = builder.addSeparator(Messages.getString("TSMuxerVideo.3"),
                FormLayoutUtil.flip(cc.xyw(2, 1, 1), colSpec, orientation));
        cmp = (JComponent) cmp.getComponent(0);
        cmp.setFont(cmp.getFont().deriveFont(Font.BOLD));

        tsmuxerforcefps = new JCheckBox(Messages.getString("TSMuxerVideo.2"));
        tsmuxerforcefps.setContentAreaFilled(false);
        if (configuration.isTsmuxerForceFps()) {
            tsmuxerforcefps.setSelected(true);
        }
        tsmuxerforcefps.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                configuration.setTsmuxerForceFps(e.getStateChange() == ItemEvent.SELECTED);
            }
        });
        builder.add(tsmuxerforcefps, FormLayoutUtil.flip(cc.xy(2, 3), colSpec, orientation));

        muxallaudiotracks = new JCheckBox(Messages.getString("TSMuxerVideo.19"));
        muxallaudiotracks.setContentAreaFilled(false);
        if (configuration.isMuxAllAudioTracks()) {
            muxallaudiotracks.setSelected(true);
        }

        muxallaudiotracks.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                configuration.setMuxAllAudioTracks(e.getStateChange() == ItemEvent.SELECTED);
            }
        });
        builder.add(muxallaudiotracks, FormLayoutUtil.flip(cc.xy(2, 5), colSpec, orientation));

        JPanel panel = builder.getPanel();

        // Apply the orientation to the panel and all components in it
        panel.applyComponentOrientation(orientation);

        return panel;
    }

    @Override
    public boolean isInternalSubtitlesSupported() {
        return false;
    }

    @Override
    public boolean isExternalSubtitlesSupported() {
        return false;
    }

    @Override
    public boolean isPlayerCompatible(RendererConfiguration mediaRenderer) {
        return mediaRenderer != null && mediaRenderer.isMuxH264MpegTS();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isCompatible(DLNAResource dlna) {
        if (!(PlayerUtil.isVideo(dlna, Format.Identifier.MKV) || PlayerUtil.isVideo(dlna, Format.Identifier.MPG))) {
            return false;
        }

        /*
         * Notes:
         *
         * 1) isCompatible is used for two separate tasks: 1) selection
         * of #--TRANSCODE--# folder entries (PlayerFactory.getPlayers)
         * and 2) assignment of the default player for a resource
         * (PlayerFactory.getPlayer)
         *
         * 2) for resources that aren't in the #--TRANSCODE--# folder, it's understood
         * that tsMuxeR will only stream the first audio track and that it won't/can't
         * stream subtitles, so no need to check either
         */
        if (dlna.isNoName()) { // #--TRANSCODE--# folder entry
            DLNAMediaAudio dlnaMediaAudio = dlna.getMediaAudio();

            // media is non-null and audioTrackList is non-empty by definition if
            // mediaAudio has been set (see FileTranscodeVirtualFolder)
            if ((dlnaMediaAudio != null)
                    && !dlnaMediaAudio.toString().equals(dlna.getMedia().getFirstAudioTrack().toString())) {
                // PMS only supports playback of the first audio track for tsMuxeR
                return false;
            }

            // XXX why is tsMuxeR compatible if the subtitle language is null?
            if ((dlna.getMediaSubtitle() != null) && (dlna.getMediaSubtitle().getLang() != null)) {
                return false;
            }
        }

        return true;
    }
}