如何正确检测、解码和播放无线电流?

Posted

技术标签:

【中文标题】如何正确检测、解码和播放无线电流?【英文标题】:How to properly detect, decode and play a radio stream? 【发布时间】:2014-08-27 17:25:53 【问题描述】:

我目前正在尝试用 Java 编写一个类似于点唱机的应用程序,它能够播放任何可能的音频源,但在尝试播放无线电流时遇到了一些困难。

对于播放,我使用来自 JavaZoom 的JLayer,只要目标是直接媒体文件或直接媒体流(我可以播放 PCM、MP3 和 OGG),它就可以正常工作。但是,当我尝试播放包含诸如 m3u/pls 文件之类的前媒体数据(我可以通过预先添加检测来修复)或在网页存在时在端口 80 上流式传输的数据的无线电流时遇到了困难相同的位置和传输的媒体取决于请求的类型。在后一种情况下,每当我尝试流式传输媒体时,我都会获取 html 数据。

隐藏在网页后面的流的示例链接:http://stream.t-n-media.de:8030 这可以在 VLC 中播放,但如果您将其放入浏览器或我的应用程序中,您将收到一个 HTML 文件。

有吗:

我可以使用现成的免费解决方案来代替 JLayer?最好是开源的,这样我可以研究它? 可以帮助我自己编写解决方案的教程吗? 或者谁能给我一个关于如何正确检测/请求媒体流的示例?

提前致谢!

【问题讨论】:

MP3SPI 怎么样?有关流式传输的示例,请参阅 this。 我添加了一个我无法播放的无线电流示例。 【参考方案1】:

我知道这个答案来晚了,但我遇到了同样的问题:我想播放 MP3 和 AAC 音频,还希望用户插入 PLS/M3U 链接。这是我所做的: 首先我尝试使用简单的文件名来解析类型:

import de.webradio.enumerations.FileExtension;

import java.net.URL;

public class FileExtensionParser 
    /**
     *Parses a file extension
     * @param filenameUrl the url
     * @return the filename. if filename cannot be determined by file extension, Apache Tika parses by live detection
     */
    public FileExtension parseFileExtension(URL filenameUrl) 
        String filename = filenameUrl.toString();
        if (filename.endsWith(".mp3")) 
            return FileExtension.MP3;
         else if (filename.endsWith(".m3u") || filename.endsWith(".m3u8")) 
            return FileExtension.M3U;
         else if (filename.endsWith(".aac")) 
            return FileExtension.AAC;
         else if(filename.endsWith((".pls"))) 
            return FileExtension.PLS;
        
        URLTypeParser parser = new URLTypeParser();
        return parser.parseByContentDetection(filenameUrl);
    

如果失败,我会使用 Apache Tika 进行一种实时检测:

public class URLTypeParser 


    /** This class uses Apache Tika to parse an URL using her content
     *
     * @param url the webstream url
     * @return the detected file encoding: MP3, AAC or unsupported
     */

    public FileExtension parseByContentDetection(URL url) 
        try 
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            InputStream in = connection.getInputStream();
            BodyContentHandler handler = new BodyContentHandler();
            AudioParser parser = new AudioParser();
            Metadata metadata = new Metadata();
            parser.parse(in, handler, metadata);
            return parseMediaType(metadata);
         catch (IOException e) 
            e.printStackTrace();
         catch (TikaException e) 
            e.printStackTrace();
         catch (SAXException e) 
            e.printStackTrace();
        
        return FileExtension.UNSUPPORTED_TYPE;
    

    private FileExtension parseMediaType(Metadata metadata) 
        String parsedMediaType = metadata.get("encoding");
        if (parsedMediaType.equalsIgnoreCase("aac")) 
            return FileExtension.AAC;
         else if (parsedMediaType.equalsIgnoreCase("mpeg1l3")) 
            return FileExtension.MP3;
        
        return FileExtension.UNSUPPORTED_TYPE;
    


这也将解决 HTML 问题,因为该方法将为 HTML 内容返回 FileExtension.UNSUPPORTED。 我将这些类与工厂模式结合在一起,效果很好。实时检测仅需两秒左右。

我认为这不会再对您有所帮助,但是由于我挣扎了将近三周,因此我想提供一个可行的答案。你可以在github上看到整个项目:https://github.com/Seppl2202/webradio

【讨论】:

【参考方案2】:
import java.io.*;
import java.net.*;
import javax.sound.sampled.*;
import javax.sound.midi.*;

/**
 * This class plays sounds streaming from a URL: it does not have to preload
 * the entire sound into memory before playing it. It is a command-line
 * application with no gui. It includes code to convert ULAW and ALAW
 * audio formats to PCM so they can be played. Use the -m command-line option
 * before MIDI files.
 */

public class PlaySoundStream 
    // Create a URL from the command-line argument and pass it to the 
    // right static method depending on the presence of the -m (MIDI) option.
    public static void main(String[  ] args) throws Exception 
        if (args[0].equals("-m")) streamMidiSequence(new URL(args[1]));
        else streamSampledAudio(new URL(args[0]));

        // Exit explicitly.
        // This is needed because the audio system starts background threads.
        System.exit(0);
    

    /** Read sampled audio data from the specified URL and play it */
    public static void streamSampledAudio(URL url)
        throws IOException, UnsupportedAudioFileException,
               LineUnavailableException
    
        AudioInputStream ain = null;  // We read audio data from here
        SourceDataLine line = null;   // And write it here.

        try 
            // Get an audio input stream from the URL
            ain=Audiosystem.getAudioInputStream(url);

            // Get information about the format of the stream
            AudioFormat format = ain.getFormat( );
            DataLine.Info info=new DataLine.Info(SourceDataLine.class,format);

            // If the format is not supported directly (i.e. if it is not PCM
            // encoded), then try to transcode it to PCM.
            if (!AudioSystem.isLineSupported(info)) 
                // This is the PCM format we want to transcode to.
                // The parameters here are audio format details that you
                // shouldn't need to understand for casual use.
                AudioFormat pcm =
                    new AudioFormat(format.getSampleRate( ), 16,
                                    format.getChannels( ), true, false);

                // Get a wrapper stream around the input stream that does the
                // transcoding for us.
                ain = AudioSystem.getAudioInputStream(pcm, ain);

                // Update the format and info variables for the transcoded data
                format = ain.getFormat( ); 
                info = new DataLine.Info(SourceDataLine.class, format);
            

            // Open the line through which we'll play the streaming audio.
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(format);  

            // Allocate a buffer for reading from the input stream and writing
            // to the line.  Make it large enough to hold 4k audio frames.
            // Note that the SourceDataLine also has its own internal buffer.
            int framesize = format.getFrameSize( );
            byte[  ] buffer = new byte[4 * 1024 * framesize]; // the buffer
            int numbytes = 0;                               // how many bytes

            // We haven't started the line yet.
            boolean started = false;

            for(;;)   // We'll exit the loop when we reach the end of stream
                // First, read some bytes from the input stream.
                int bytesread=ain.read(buffer,numbytes,buffer.length-numbytes);
                // If there were no more bytes to read, we're done.
                if (bytesread == -1) break;
                numbytes += bytesread;

                // Now that we've got some audio data to write to the line,
                // start the line, so it will play that data as we write it.
                if (!started) 
                    line.start( );
                    started = true;
                

                // We must write bytes to the line in an integer multiple of
                // the framesize.  So figure out how many bytes we'll write.
                int bytestowrite = (numbytes/framesize)*framesize;

                // Now write the bytes. The line will buffer them and play
                // them. This call will block until all bytes are written.
                line.write(buffer, 0, bytestowrite);

                // If we didn't have an integer multiple of the frame size, 
                // then copy the remaining bytes to the start of the buffer.
                int remaining = numbytes - bytestowrite;
                if (remaining > 0)
                    System.arraycopy(buffer,bytestowrite,buffer,0,remaining);
                numbytes = remaining;
            

            // Now block until all buffered sound finishes playing.
            line.drain( );
        
        finally  // Always relinquish the resources we use
            if (line != null) line.close( );
            if (ain != null) ain.close( );
        
    

    // A MIDI protocol constant that isn't defined by javax.sound.midi
    public static final int END_OF_TRACK = 47;

    /* MIDI or RMF data from the specified URL and play it */
    public static void streamMidiSequence(URL url)
        throws IOException, InvalidMidiDataException, MidiUnavailableException
    
        Sequencer sequencer=null;     // Converts a Sequence to MIDI events
        Synthesizer synthesizer=null; // Plays notes in response to MIDI events

        try 
            // Create, open, and connect a Sequencer and Synthesizer
            // They are closed in the finally block at the end of this method.
            sequencer = MidiSystem.getSequencer( );
            sequencer.open( );  
            synthesizer = MidiSystem.getSynthesizer( );
            synthesizer.open( );
            sequencer.getTransmitter( ).setReceiver(synthesizer.getReceiver( ));

            // Specify the InputStream to stream the sequence from
            sequencer.setSequence(url.openStream( ));  

            // This is an arbitrary object used with wait and notify to 
            // prevent the method from returning before the music finishes
            final Object lock = new Object( );

            // Register a listener to make the method exit when the stream is 
            // done. See Object.wait( ) and Object.notify( )
            sequencer.addMetaEventListener(new MetaEventListener( ) 
                    public void meta(MetaMessage e) 
                        if (e.getType( ) == END_OF_TRACK) 
                            synchronized(lock)  
                                lock.notify( );
                            
                        
                    
                );

            // Start playing the music
            sequencer.start( );

            // Now block until the listener above notifies us that we're done.
            synchronized(lock) 
                while(sequencer.isRunning( )) 
                    try  lock.wait( );  catch(InterruptedException e)   
                
            
        
        finally 
            // Always relinquish the sequencer, so others can use it.
            if (sequencer != null) sequencer.close( );
            if (synthesizer != null) synthesizer.close( );
        
    

我在我的一个处理音频流的项目中使用了这段代码,并且运行良好。

此外,您可以在此处查看类似示例: Java Audio Example

【讨论】:

这是关于我目前已经实现的,加上由于 JLayer 添加了 MP3 和 OGG 流,但我的和你的都不能播放广播流。【参考方案3】:

只要阅读AudioSystem 的 javadoc 给我一个想法。

getAudioInputStream 还有一个签名:你可以给它一个 InputStream 而不是 URL。

因此,尝试自己获取输入流并添加所需的标头,以便获取流而不是 html 内容:

URLConnection uc = url.openConnection();
uc.setRequestProperty("<header name here>", "<header value here>");

InputStream in = uc.getInputStream();

ain=AudioSystem.getAudioInputStream(in);

希望对您有所帮助。

【讨论】:

以上是关于如何正确检测、解码和播放无线电流?的主要内容,如果未能解决你的问题,请参考以下文章

android 无线电流电话状态扬声器

无线电流 WindowsPhone 应用程序

如何创建用于处理无线电流的 Android 应用程序

如何在 iPhone/iOS 中播放 xiph.org 音频编解码器?

达菲系统如何无网连接

在 iOS 6 应用程序中播放广播链接