All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.sbtqa.monte.media.audio.PCMAudioCodec Maven / Gradle / Ivy

/* @(#)PCMAudioCodec.java
 * Copyright © 2011 Werner Randelshofer, Switzerland. 
 * You may only use this software in accordance with the license terms.
 */
package ru.sbtqa.monte.media.audio;

import java.io.EOFException;
import java.io.IOException;
import static java.lang.System.arraycopy;
import java.nio.ByteOrder;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import ru.sbtqa.monte.media.AbstractAudioCodec;
import static ru.sbtqa.monte.media.AudioFormatKeys.*;
import ru.sbtqa.monte.media.Buffer;
import static ru.sbtqa.monte.media.BufferFlag.*;
import ru.sbtqa.monte.media.Format;
import static ru.sbtqa.monte.media.FormatKeys.MediaType.AUDIO;
import ru.sbtqa.monte.media.io.ByteArrayImageInputStream;
import ru.sbtqa.monte.media.io.ByteArrayImageOutputStream;

/**
 * {@code PCMAudioCodec} performs sign conversion, endian conversion and
 * quantization conversion of PCM audio data.
 * 
 * Does not perform sampling rate conversion or channel conversion.
 * 
 * FIXME Maybe create separate subclasses for AVI PCM and QuickTime PCM.
 *
 * @author Werner Randelshofer
 * @version $Id: PCMAudioCodec.java 364 2016-11-09 19:54:25Z werner $
 */
public class PCMAudioCodec extends AbstractAudioCodec {

    private final static int UNSIGNED_BASE = 128;

    public PCMAudioCodec() {
        super(new Format[]{
            new Format(MediaTypeKey, AUDIO,//
            EncodingKey, ENCODING_PCM_SIGNED,//
            MimeTypeKey, MIME_JAVA,//
            SignedKey, true),
            new Format(MediaTypeKey, AUDIO,//
            EncodingKey, ENCODING_PCM_UNSIGNED,//
            MimeTypeKey, MIME_JAVA,//
            SignedKey, false),//
        });
        name = "PCM Codec";
    }

    protected PCMAudioCodec(Format[] supportedInputFormats, Format[] supportedOutputFormats) {
        super(supportedInputFormats, supportedOutputFormats);
    }

    protected PCMAudioCodec(Format[] supportedInputAndOutputFormats) {
        super(supportedInputAndOutputFormats, supportedInputAndOutputFormats);
    }

    @Override
    public int process(Buffer in, Buffer out) {
        out.setMetaTo(in);
        if (in.isFlag(DISCARD)) {
            return CODEC_OK;
        }

        Format inFormat = in.format;
        Format outFormat = outputFormat;
        if (inFormat.get(SampleRateKey) == null || !inFormat.get(SampleRateKey).equals(outFormat.get(SampleRateKey))) {
            out.setFlag(DISCARD);
            return CODEC_FAILED;
            //throw new UnsupportedOperationException("Sample Rate conversion not supported. in:" + inFormat + ", out:" + outFormat);
        }
        if (inFormat.get(ChannelsKey) != outFormat.get(ChannelsKey)) {
            out.setFlag(DISCARD);
            return CODEC_FAILED;
            //throw new UnsupportedOperationException("Channel conversion not supported. in:" + inFormat + ", out:" + outFormat);
        }
        String inEnc = inFormat.get(EncodingKey);
        String outEnc = outFormat.get(EncodingKey);

        boolean fixSilenceBug = inFormat.get(SilenceBugKey, false);

        /*
         if (!supportedEncodings.contains(inEnc)
         || !supportedEncodings.contains(outEnc)) {
         throw new UnsupportedOperationException("Unsupported encoding. in:" + inFormat + ", out:" + outFormat);
         }*/
        byte[] inData = (byte[]) in.data;
        byte[] outData = (out.data instanceof byte[]) ? (byte[]) out.data : new byte[inData.length];
        if (outData.length < inData.length * outFormat.get(FrameSizeKey) / inFormat.get(FrameSizeKey)) {
            outData = new byte[inData.length * outFormat.get(FrameSizeKey) / inFormat.get(FrameSizeKey)];
        }

        // Fast array copy if formats are identical 
        // or if 8 bit data with endian differences
        if (toAudioFormat(inFormat).matches(toAudioFormat(outFormat))) {
            arraycopy(inData, in.offset, outData, 0, in.length);
        } else {

            // Byte order conversion is done by ImageInputStream/ImageOutputStream.
            ByteOrder inOrder = inFormat.get(ByteOrderKey);
            boolean inSigned = inFormat.get(SignedKey);
            ByteArrayImageInputStream inStream = new ByteArrayImageInputStream(inData, in.offset, in.length, inOrder);
            ByteOrder outOrder = outFormat.get(ByteOrderKey);
            boolean outSigned = outFormat.get(SignedKey);
            ByteArrayImageOutputStream outStream = new ByteArrayImageOutputStream(outData, outOrder);
            try {
                // Now, we only have to care about sign conversion and 
                // quantization conversion
                int inSS = inFormat.get(SampleSizeInBitsKey);
                int outSS = outFormat.get(SampleSizeInBitsKey);

                switch ((inSS << 16) | outSS) {
                    case (16 << 16) | 16:
                        if (inSigned == outSigned) {
                            write16To16(inStream, outStream);
                        } else if (inSigned) {
                            write16STo16U(inStream, outStream);
                        } else {
                            write16UTo16S(inStream, outStream);
                        }
                        break;
                    case (16 << 16) | 8:
                        if (inSigned == outSigned) {
                            throw new UnsupportedOperationException("Unsupported sample size. in:" + inFormat + ", out:" + outFormat);
                        } else if (inSigned) {
                            write16STo8U(inStream, outStream);
                        } else {
                            throw new UnsupportedOperationException("Unsupported sample size. in:" + inFormat + ", out:" + outFormat);
                        }
                        break;
                    case (8 << 16) | 8:
                        if (inSigned == outSigned) {
                            write8STo8S(inStream, outStream, fixSilenceBug);
                        } else if (inSigned) {
                            write8STo8U(inStream, outStream, fixSilenceBug);
                        } else if (outSigned) {
                            write8UTo8S(inStream, outStream, fixSilenceBug);
                        }
                        break;
                    default:
                        // FIXME - The PCM Audio Codec should handle unsupported
                        //         sample size when the output format is set.
                        throw new UnsupportedOperationException("Unsupported sample size. in:" + inFormat + ", out:" + outFormat);
                    //out.setFlag(Buffer.FLAG_DISCARD);
                    //return BUFFER_PROCESSED_FAILED;

                }
            } catch (IOException ex) {
                out.flags.add(DISCARD);
                return CODEC_FAILED;
                //throw new InternalError(ex.getMessage());
            }

        }

        // Configure the buffer
        out.flags.add(KEYFRAME);
        out.format = outFormat;
        out.data = outData;
        out.offset = 0;
        out.length = in.length;
        return CODEC_OK;
    }

    /**
     * 16-bit audio data in and out. No sign conversion. Endian conversion is
     * performed by the stream objects.
     *
     * @param in TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write16To16(ImageInputStream in, ImageOutputStream out) throws IOException {
        try {
            while (true) {
                out.writeShort(in.readShort());
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * 16-bit audio data unsigned in and signed out. Endian conversion is
     * performed by the stream objects.
     *
     * @param in TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write16UTo16S(ImageInputStream in, ImageOutputStream out) throws IOException {
        try {
            while (true) {
                out.writeShort((in.readShort() & 0xffff) - (1 << 15));
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * 16-bit audio data signed in and unsigned out. Endian conversion is
     * performed by the stream objects.
     *
     * @param in TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write16STo16U(ImageInputStream in, ImageOutputStream out) throws IOException {
        try {
            while (true) {
                out.writeShort(in.readShort() + (1 << 15));
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * Signed 16-bit audio data in, unsigned 8-bit out. Endian conversion is
     * performed by the stream objects.
     *
     * @param in TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write16STo8U(ImageInputStream in, ImageOutputStream out) throws IOException {
        try {
            while (true) {
                out.writeByte((in.readShort() >> 8) + UNSIGNED_BASE);
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * 8-bit audio data in and out. No sign conversion.
     *
     * @param in TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write8To8(ImageInputStream in, ImageOutputStream out) throws IOException {
        try {
            byte[] buf = new byte[512];
            while (true) {
                int count = in.read(buf, 0, buf.length);
                if (count == -1) {
                    break;
                }
                out.write(buf, 0, count);
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * Unsigned 8-bit audio data in, signed out.
     *
     * @param in TODO
     * @param fixSilenceBug TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write8UTo8S(ImageInputStream in, ImageOutputStream out, boolean fixSilenceBug) throws IOException {
        try {
            byte[] buf = new byte[512];
            while (true) {
                int count = in.read(buf, 0, buf.length);
                if (count == -1) {
                    break;
                }
                if (fixSilenceBug) {
                    for (int i = 0; i < count; i++) {
                        if (buf[i] == 0) {
                            buf[i] = (byte) UNSIGNED_BASE;
                        }
                        buf[i] = (byte) ((buf[i] & 0xff) - UNSIGNED_BASE);
                    }
                } else {
                    for (int i = 0; i < count; i++) {
                        buf[i] = (byte) ((buf[i] & 0xff) - UNSIGNED_BASE);
                    }
                }
                out.write(buf, 0, count);
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * Signed 8-bit audio data in, unsigned out.
     *
     * @param in TODO
     * @param fixSilenceBug TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write8STo8U(ImageInputStream in, ImageOutputStream out, boolean fixSilenceBug) throws IOException {
        try {
            byte[] buf = new byte[512];
            while (true) {
                int count = in.read(buf, 0, buf.length);
                if (count == -1) {
                    break;
                }
                if (fixSilenceBug) {
                    for (int i = 0; i < count; i++) {
                        // FIXME - For some reason, the Java sound system records
                        //         silence as -128 instead of 0.
                        buf[i] = (byte) (buf[i] == -128 ? -UNSIGNED_BASE : buf[i] + UNSIGNED_BASE);
                    }
                } else {
                    for (int i = 0; i < count; i++) {
                        buf[i] = (byte) (buf[i] + UNSIGNED_BASE);
                    }
                }

                out.write(buf, 0, count);
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }

    /**
     * Signed 8-bit audio data in, signed out.
     *
     * @param in TODO
     * @param fixSilenceBug TODO
     * @param out TODO
     * @throws java.io.IOException TODO
     */
    protected void write8STo8S(ImageInputStream in, ImageOutputStream out, boolean fixSilenceBug) throws IOException {
        try {
            byte[] buf = new byte[512];
            while (true) {
                int count = in.read(buf, 0, buf.length);
                if (count == -1) {
                    break;
                }
                if (fixSilenceBug) {
                    for (int i = 0; i < count; i++) {
                        // FIXME - For some reason, the Java sound system records
                        //         silence as -128 instead of 0.
                        buf[i] = (buf[i] == -128 ? 0 : buf[i]);
                    }
                }
                out.write(buf, 0, count);
            }
        } catch (EOFException e) {
            // end of data reached
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy