我正在尝试用Java中的Xuggler将aac / wav / wma音频文件转换为mp3。
不幸的是,我的质量大大降低。我的输入文件大小约为7MB,输出文件大小仅为1.5MB。
采样率设置为44100 Hz,是否还要设置其他参数?
感谢您的回答。
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);
}
}
答案 0 :(得分:6)
我会做这样的事情:
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);
}
输入是要转换的文件(aac / wav / wma),输出是一个新的.mp3文件(Xuggler通过扩展名找出转换)。
您可以提高质量,增加kbps(即320 kbps需要传递320000)。
希望有所帮助: - )
仅供参考:对于Java项目,如果您还没有这样做,则需要导入以下内容:
import com.xuggle.mediatool.MediaListenerAdapter;
import com.xuggle.mediatool.event.IAddStreamEvent;
import com.xuggle.xuggler.IStreamCoder;
答案 1 :(得分:0)
我不确定具体选项及其作用,但请查看javadoc for IStreamCoder。您可能想要使用其他各种选项。如果你想要完全控制,你甚至可以使用setFlags()
方法直接在ffmpeg上设置标志(Xuggler在其下面使用)。
答案 2 :(得分:0)
当你有一个带有封面(png)的mp3时要小心,你可能会因为尝试将视频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( );
}