Аудио конвертирование с помощью Xuggler

Я пытаюсь конвертировать аудио файлы aac/wav/wma в mp3 с помощью Xuggler на Java.

К сожалению, у меня большая потеря качества. Размер моего входного файла составляет около 7 МБ, а размер выходного файла - всего 1,5 МБ.

Частота дискретизации установлена ​​на 44100 Гц, есть ли другие параметры для установки?

Спасибо за ответ.

  if (args.length <= 1)
        throw new IllegalArgumentException("must pass an input filename and output filename as argument");

    IMediaWriter writer = ToolFactory.makeWriter(args[1]);

    String filename = args[0];

    // Create a Xuggler container object
    IContainer container = IContainer.make();

    // Open up the container
    if (container.open(filename, IContainer.Type.READ, null) < 0)
        throw new IllegalArgumentException("could not open file: " + filename);

    // query how many streams the call to open found
    int numStreams = container.getNumStreams();

    // and iterate through the streams to find the first audio stream
    int audioStreamId = -1;
    IStreamCoder audioCoder = null;
    for(int i = 0; i < numStreams; i++)
    {
        // Find the stream object
        IStream stream = container.getStream(i);
        // Get the pre-configured decoder that can decode this stream;
        IStreamCoder coder = stream.getStreamCoder();

        if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO)
        {
            audioStreamId = i;
            audioCoder = coder;
            audioCoder.setBitRate(container.getBitRate());

            break;
        }
    }

    if (audioStreamId == -1)
        throw new RuntimeException("could not find audio stream in container: "+filename);

    /* We read only AAC file for the moment */
    if(audioCoder.getCodecID() != ICodec.ID.CODEC_ID_AAC 
        && audioCoder.getCodecID() != ICodec.ID.CODEC_ID_WAVPACK 
        && audioCoder.getCodecID() != ICodec.ID.CODEC_ID_WMAV1
        && audioCoder.getCodecID() != ICodec.ID.CODEC_ID_WMAV2
        && audioCoder.getCodecID() != ICodec.ID.CODEC_ID_WMAPRO
        && audioCoder.getCodecID() != ICodec.ID.CODEC_ID_WMAVOICE)
    {
        System.out.println("Read only AAC, WAV or WMA files");
        System.exit(1);
    }

    audioCoder.setSampleFormat(IAudioSamples.Format.FMT_S16);
    /*
     * Now we have found the audio stream in this file.  Let's open up our decoder so it can
     * do work.
     */
    if (audioCoder.open() < 0)
        throw new RuntimeException("could not open audio decoder for container: "+filename);

    int streamIndex = writer.addAudioStream(0, 0, audioCoder.getChannels(), audioCoder.getSampleRate());


    System.out.println("audio Frame size : "+audioCoder.getAudioFrameSize());


    /*
     * Now, we start walking through the container looking at each packet.
     */
    IPacket packet = IPacket.make();

    while(container.readNextPacket(packet) >= 0)
    {
        /*
         * Now we have a packet, let's see if it belongs to our audio stream
         */
        if (packet.getStreamIndex() == audioStreamId)
        {
            /*
             * We allocate a set of samples with the same number of channels as the
             * coder tells us is in this buffer.
             * 
             * We also pass in a buffer size (1024 in our example), although Xuggler
             * will probably allocate more space than just the 1024 (it's not important why).
             */

            IAudioSamples samples = IAudioSamples.make(512, audioCoder.getChannels(),IAudioSamples.Format.FMT_S16 );

            /*
             * A packet can actually contain multiple sets of samples (or frames of samples
             * in audio-decoding speak).  So, we may need to call decode audio multiple
             * times at different offsets in the packet's data.  We capture that here.
             */
            int offset = 0;

            /*
             * Keep going until we've processed all data
             */         

            while(offset < packet.getSize())
            {
                int bytesDecoded = audioCoder.decodeAudio(samples, packet, offset);
                if (bytesDecoded < 0)
                    throw new RuntimeException("got error decoding audio in: " + filename);

                offset += bytesDecoded;

                /*
                 * Some decoder will consume data in a packet, but will not be able to construct
                 * a full set of samples yet.  Therefore you should always check if you
                 * got a complete set of samples from the decoder
                 */                                     
                if (samples.isComplete())
                {
                    writer.encodeAudio(streamIndex, samples);   
                }
            }
        }
        else
        {
            /*
             * This packet isn't part of our audio stream, so we just silently drop it.
             */
            do {} while(false);
        }
    }

3 ответа

Я сделаю что-то вроде этого:

public void convertToMP3(File input, File output, int kbps) { //modify on your convenience
    // create a media reader
    IMediaReader mediaReader = ToolFactory.makeReader(input.getPath());

    // create a media writer
    IMediaWriter mediaWriter = ToolFactory.makeWriter(output.getPath(), mediaReader);

    // add a writer to the reader, to create the output file
    mediaReader.addListener(mediaWriter);

    // add a IMediaListner to the writer to change bit rate
    mediaWriter.addListener(new MediaListenerAdapter() {
        @Override
        public void onAddStream(IAddStreamEvent event) {
            IStreamCoder streamCoder = event.getSource().getContainer().getStream(event.getStreamIndex()).getStreamCoder();
            streamCoder.setFlag(IStreamCoder.Flags.FLAG_QSCALE, false);
            streamCoder.setBitRate(kbps);
            streamCoder.setBitRateTolerance(0);
            }
        });

    // read and decode packets from the source file and
    // and dispatch decoded audio and video to the writer
    while (mediaReader.readPacket() == null);
}

input - это файл (aac/wav/wma), который вы хотите конвертировать, а на выходе - новый файл.mp3 (Xuggler определяет преобразование по расширению).

Вы можете повысить качество, увеличивая кбит / с (т. Е. Для 320 кбит / с вам нужно передать 320000).

Надеюсь, это поможет:-)

К вашему сведению: для проектов Java вам необходимо импортировать следующее, если вы еще этого не сделали:

import com.xuggle.mediatool.MediaListenerAdapter;
import com.xuggle.mediatool.event.IAddStreamEvent;
import com.xuggle.xuggler.IStreamCoder;

Я не уверен в точных параметрах и что они делают, но взгляните на Javadoc для IStreamCoder. Есть и другие варианты, с которыми вы можете поиграть. Вы даже можете установить флаги на ffmpeg напрямую (который Xuggler использует внизу) с помощью setFlags() метод, если вы хотите полный контроль.

Будьте осторожны, когда у вас есть mp3 с обложкой (png), вы можете столкнуться с ошибками, потому что вы пытаетесь отправить видео png поток в аудиопоток. Используя ISamples и читая пакет с помощью if ( packet.getStreamIndex() == audioStreamId) {} дает лучший контроль над потоком, который вы используете. проверьте мой полный код:

private static void streamToSource( OutputStream source, Path path ) throws IOException {

    byte[] buffer = new byte[4096];
    PipedInputStream pis = new PipedInputStream( );
    PipedOutputStream pos = new PipedOutputStream( pis );
    convertToMP3Xuggler( path, pos );

    System.out.println( "start streaming" );
    int nRead = 0;
    while ( ( nRead = pis.read( buffer ) ) != -1 ) {
        source.write( buffer,0 , nRead );
    }
    pis.close( );

    System.out.println( "end : " + path );

}

private static void convertToMP3Xuggler( Path path, PipedOutputStream pos ) throws FileNotFoundException {

    // create a media reader
    // final IMediaReader mediaReader = ToolFactory.makeReader( XugglerIO.map( new FileInputStream( path.toFile( ) ) ) );

    // create a media writer
    // IMediaWriter mediaWriter = ToolFactory.makeWriter( XugglerIO.map( XugglerIO.generateUniqueName( os, ".mp3" ), os ), mediaReader );
    IMediaWriter mediaWriter = ToolFactory.makeWriter( XugglerIO.map( pos ) );
    // manually set the container format (because it can't detect it by filename anymore)


    IContainerFormat containerFormat = IContainerFormat.make( );
    containerFormat.setOutputFormat( "mp3", null, "audio/mp3" );
    mediaWriter.getContainer( ).setFormat( containerFormat );

    System.out.println( "file = " + path.toFile( ).toString( ) );

    IContainer audioContainer = IContainer.make( );
    audioContainer.open( path.toFile( ).toString( ), IContainer.Type.READ, null );

    System.out.println( "streams= " + audioContainer.getNumStreams( ) );
    System.out.println( "# Duration (ms): " + ( ( audioContainer.getDuration( ) == Global.NO_PTS ) ? "unknown" : "" + audioContainer.getDuration( ) / 1000 ) );
    System.out.println( "# File size (bytes): " + audioContainer.getFileSize( ) );
    System.out.println( "# Bit rate: " + audioContainer.getBitRate( ) );
    int audioStreamId = -1;


    for ( int i = 0; i < audioContainer.getNumStreams( ); i++ ) {
        // Find the stream object
        IStream stream = audioContainer.getStream( i );
        // Get the pre-configured decoder that can decode this stream;
        IStreamCoder coder = stream.getStreamCoder( );
        if ( coder.getCodecType( ) == ICodec.Type.CODEC_TYPE_AUDIO ) {
            audioStreamId = i;
            break;
        }
    }
    if ( audioStreamId < 0 ) {
        throw new IllegalArgumentException( "cannot find audio stream in the current file : " + path.toString( ) );
    }
    System.out.println( "found audio stream = " + audioStreamId );

    IStreamCoder coderAudio = audioContainer.getStream( audioStreamId ).getStreamCoder( );

    if ( coderAudio.open( null, null ) < 0 ) {
        throw new RuntimeException( "Cant open audio coder" );
    }
    coderAudio.setSampleFormat( IAudioSamples.Format.FMT_S16 );

    System.out.println( "bitrate from reading = " + audioContainer.getBitRate( ) );
    System.out.println( "bitrate from reading = " + coderAudio.getBitRate( ) );

    int streamIndex = mediaWriter.addAudioStream( 0, 0, coderAudio.getChannels( ), coderAudio.getSampleRate( ) );
    IStreamCoder writerCoder = mediaWriter.getContainer( ).getStream( streamIndex ).getStreamCoder( );
    writerCoder.setFlag( IStreamCoder.Flags.FLAG_QSCALE, false );
    writerCoder.setBitRate( BITRATE * 1000 );
    writerCoder.setBitRateTolerance( 0 );
    System.out.println( "bitrate for output = " + writerCoder.getBitRate( ) );

    IPacket packet = IPacket.make( );

    runInThread( path, pos, mediaWriter, audioContainer, audioStreamId, coderAudio, streamIndex, packet );

}

private static void runInThread( Path path, PipedOutputStream pos, IMediaWriter mediaWriter, IContainer audioContainer, int audioStreamId, IStreamCoder coderAudio, int streamIndex, IPacket packet ) {

    new Thread( ) {
        @Override
        public void run( ) {

            while ( audioContainer.readNextPacket( packet ) >= 0 ) {
                /*
                 * Now we have a packet, let's see if it belongs to our audio stream
                 */
                if ( packet.getStreamIndex( ) == audioStreamId ) {
                    /*
                     * We allocate a set of samples with the same number of channels as the
                     * coder tells us is in this buffer.
                     * We also pass in a buffer size (4096 in our example), although Xuggler
                     * will probably allocate more space than just the 4096 (it's not important why).
                     */

                    IAudioSamples samples = IAudioSamples.make( 4096, coderAudio.getChannels( ), IAudioSamples.Format.FMT_S16 );

                    /*
                     * A packet can actually contain multiple sets of samples (or frames of samples
                     * in audio-decoding speak). So, we may need to call decode audio multiple
                     * times at different offsets in the packet's data. We capture that here.
                     */
                    int offset = 0;

                    /*
                     * Keep going until we've processed all data
                     */

                    while ( offset < packet.getSize( ) ) {
                        int bytesDecoded = coderAudio.decodeAudio( samples, packet, offset );
                        if ( bytesDecoded < 0 ) {
                            System.out.println( "decode error in : " + path + " bytesDecoded =" + bytesDecoded + " offset=" + offset + " packet=" + packet );
                            break;
                            //                                throw new RuntimeException( "got error decoding audio in: " + path );
                        }

                        offset += bytesDecoded;

                        //                            System.out.println( "pktSize = " + packet.getSize( ) + "  offset = " + offset + " samplesComplete = " + samples.isComplete( ) );

                        /*
                         * Some decoder will consume data in a packet, but will not be able to construct
                         * a full set of samples yet. Therefore you should always check if you
                         * got a complete set of samples from the decoder
                         */
                        if ( samples.isComplete( ) ) {
                            mediaWriter.encodeAudio( streamIndex, samples );
                        }
                    }
                }
            }
            coderAudio.close( );
            audioContainer.close( );
            mediaWriter.close( );
            try {
                pos.close( );
            } catch ( IOException e ) {
                e.printStackTrace( );
            }
        }

    }.start( );
}
Другие вопросы по тегам