如何在不发出单独请求的情况下将元数据和音轨从广播流中分离出来

Posted

技术标签:

【中文标题】如何在不发出单独请求的情况下将元数据和音轨从广播流中分离出来【英文标题】:How to separate metadata and track from a shoutcast stream without making separate requests 【发布时间】:2016-09-29 23:49:30 【问题描述】:

我制作了一个运行良好的收音机应用程序。我也可以播放广播流并获取元数据。流媒体服务来自直播。

唯一的问题是,我将 URL 作为数据源添加到媒体播放器,然后每 5 秒获取一次标题和艺术家。

有什么办法,我可以只发出一个 HTTP 请求,然后将音频和元数据拆分,然后发送到媒体播放器?

获取元数据的代码。

private void retreiveMetadata() throws IOException 

    int metaDataOffset = 0;

    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
        .addHeader("Icy-MetaData", "1")
        .addHeader("Connection", "close")
        .addHeader("Accept", "")
        .url(streamUrl)
        .build();

    request.headers("");


    Response response = client.newCall(request).execute();
    InputStream stream = response.body().byteStream();

    //Map<String, List<String>> headers = response..getHeaderFields();

    if (!response.headers("icy-metaint").equals("")) 
        // Headers are sent via HTTP

        String icyMetaInt = response.headers("icy-metaint").toString();
        icyMetaInt = icyMetaInt.replace("[", "");
        icyMetaInt = icyMetaInt.replace("]", "");

        metaDataOffset = Integer.parseInt(icyMetaInt);
     else 

        // Headers are sent within a stream
        StringBuilder strHeaders = new StringBuilder();
        char c;
        while ((c = (char)stream.read()) != -1) 
            strHeaders.append(c);
            if (strHeaders.length() > 5 && (strHeaders.substring((strHeaders.length() - 4), strHeaders.length()).equals("\r\n\r\n"))) 
                // end of headers
                break;
            
        

        // Match headers to get metadata offset within a stream
        Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n");
        Matcher m = p.matcher(strHeaders.toString());

        if (m.find()) 
            metaDataOffset = Integer.parseInt(m.group(2));
        
    

    // In case no data was sent
    if (metaDataOffset == 0) 
        isError = true;
        return;
    

    // Read metadata
    int b;
    int count = 0;
    int metaDataLength = 4080; // 4080 is the max length
    boolean inData = false;
    StringBuilder metaData = new StringBuilder();
    // Stream position should be either at the beginning or right after headers
    while ((b = stream.read()) != -1) 
        count++;

        // Length of the metadata
        if (count == metaDataOffset + 1) 
            metaDataLength = b * 16;
        

        if (count > metaDataOffset + 1 && count < (metaDataOffset + metaDataLength)) 
            inData = true;
         else 
            inData = false;
        

        if (inData) 
            if (b != 0) 
                metaData.append((char)b);
            
        

        if (count > (metaDataOffset + metaDataLength)) 
            break;
        

    

    // Set the data
    metadata = IcyStreamMeta.parseMetadata(metaData.toString());

    // Close
    stream.close();


public static Map<String, String> parseMetadata(String metaString) 
    Map<String, String> metadata = new HashMap();
    String[] metaParts = metaString.split(";");
    Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'([^\\']*)\\'$");
    Matcher m;
    for (int i = 0; i < metaParts.length; i++) 
        m = p.matcher(metaParts[i]);
        if (m.find()) 
            metadata.put((String)m.group(1), (String)m.group(2));
        
    

    return metadata;

并将 url 传递给媒体播放器的数据源

String url = "http://68.68.109.106:8356/";
mp = new MediaPlayer();
mp.setAudiostreamType(AudioManager.STREAM_MUSIC);
try 

    mp.setDataSource(url);
    mp.prepare();

 catch (IllegalArgumentException e) 
    // TODO Auto-generated catch block
    e.printStackTrace();
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
 catch (SecurityException e) 
    // TODO Auto-generated catch block
    Log.e(TAG, "SecurityException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
 catch (IllegalStateException e) 
    // TODO Auto-generated catch block
    Log.e(TAG, "IllegalStateException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
 catch (IOException e) 
    // TODO Auto-generated catch block
    Log.e(TAG, "IOException");
    Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();

【问题讨论】:

是的,您只需要从流中解复用元数据。有关更多详细信息,请参阅此答案:***.com/questions/4911062/… 如果您想要 android 上 Java 的特定答案,您应该显示您当前如何获取流数据的代码。 @Brad 查看我的更新答案。谢谢。 【参考方案1】:

您尝试完成的任务并非易事。 (虽然这不是不可能的。)流的元数据仅在流的开头“下载”,因此之后更改它不会影响从流中读取“缓存”的元信息。要读取新属性,您必须重新启动流,这将获取新的标头等。(但这可能会导致流中断,因此不建议这样做。)

在声音技术中,通常使用watermarking。这是一个过程,您可以使用某种数据以非(质量)破坏的方式来丰富您的声音。 (Usage on YouTube.) 虽然很难做到,但有一些方法可以将您的信息隐藏在流中。我建议阅读this 以实现您想要达到的目标。

由于您的硬件是手机,而且并非所有 Android 设备都足够强大,因此您应该考虑一些新的 HTTP 请求。就 CPU、内存等而言,音频处理并不是一件便宜的事情。如果您这样做,还需要考虑其他一些选项。五秒轮询并不是获取信息的最佳方式,因为你可能会向用户显示虚假信息,虚假信息总比没有更糟糕。我推荐基于mqtt的服务器端推送。 Here 是一个很好的用法示例。基于这种方法而不是轮询的解决方案使用更少的流量并且更准确。

【讨论】:

以上是关于如何在不发出单独请求的情况下将元数据和音轨从广播流中分离出来的主要内容,如果未能解决你的问题,请参考以下文章

如何在不公开 API 密钥的情况下发出 POST 请求?

如何在不设置系统范围属性的情况下将 HTTP 代理用于 JAX-WS 请求?

如何将元数据从 ExoPlayer 发送到蓝牙?

如何在不调用事件的情况下将数据从子级发送到父级(vue 2)

如何在不指定每一列的情况下将整行作为参数传递给 Spark(Java)中的 UDF?

在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?