使用PHP从音频流中提取音轨信息

时间:2011-02-06 02:11:32

标签: php audio-streaming

是否可以使用PHP从音频流中提取音轨信息?我已经做了一些挖掘,我能找到的最接近的函数是stream_get_transports,但我的主机不支持通过fsockopen()的http传输,所以我将不得不做一些修补,看看该函数返回的是什么。

目前,我正在尝试从AOL流中提取艺术家和跟踪元数据。

4 个答案:

答案 0 :(得分:48)

这是一个SHOUTcast流,是的,它是可能的。它与ID3标签完全无关。我前一段时间写了一个脚本来做这个,但是找不到它了。就在上周,我帮助了另一个拥有相当完整脚本的人做同样的事情,但我不能只将源文件发布到它,因为它不是我的。如果您通过brad@musatcha.com发送电子邮件给我,我会告诉您与他联系。

无论如何,这是自己如何做的:

您需要做的第一件事就是直接连接到服务器。不要使用HTTP。好吧,你可以使用cURL,但它可能比它的价值更麻烦。您使用fsockopen()doc)与其建立联系。确保使用正确的端口。另请注意,许多Web主机将阻塞大量端口,但您通常可以使用端口80.幸运的是,所有AOL托管的SHOUTcast流都使用端口80。

现在,请像您的客户一样提出您的请求。

GET /whatever HTTP/1.0

但是,在发送<CrLf><CrLf>之前,请包含下一个标题!

Icy-MetaData:1

告诉服务器您需要元数据。现在,发送一对<CrLf>

好的,服务器会响应一堆标题,然后开始向您发送数据。在这些标题中将是icy-metaint:8192或类似的。那个8192是元间隔。这很重要,而且确实是您需要的唯一价值。它通常是8192,但并非总是如此,所以请确保实际读取此值!

基本上意味着,你将获得8192个字节的MP3数据,然后是一大块元数据,接着是8192个字节的MP3数据,接着是一大块元数据。

读取8192个字节的数据(确保不包括此计数中的标头),丢弃它们,然后读取下一个字节。该字节是元数据的第一个字节,表示元数据的长度。获取此字节的值(实际字节为ord()doc)),并将其乘以16.结果是元数据要读取的字节数。将这些字节数读入字符串变量,供您使用。

接下来,修剪此变量的值。为什么?因为字符串末尾用0x0填充(以使其均匀地适合16个字节的倍数),trim()doc)为我们处理。

你会留下这样的东西:

StreamTitle='Awesome Trance Mix - DI.fm';StreamUrl=''

我会让你选择解析它的方法。就个人而言,我可能只是在;上以2的限制进行分割,但要注意包含;的标题。我不确定转义字符方法是什么。一点实验应该对你有帮助。

完成后,不要忘记断开与服务器的连接!

有很多SHOUTcast MetaData引用。这是一个很好的:http://www.smackfu.com/stuff/programming/shoutcast.html

答案 1 :(得分:14)

检查出来:https://gist.github.com/fracasula/5781710

这是一个有点PHP功能,让你从流媒体网址中提取MP3元数据(StreamTitle)。

通常,流媒体服务器在响应中放置一个icy-metaint标头,告诉我们在流中发送元数据的频率。该函数检查该响应头,如果存在,它将用它替换interval参数。

否则该函数调用关于您的间隔的流式URL,如果没有任何元数据,则它会再次尝试从offset参数开始的递归。

<?php

/**
 * Please be aware. This gist requires at least PHP 5.4 to run correctly.
 * Otherwise consider downgrading the $opts array code to the classic "array" syntax.
 */
function getMp3StreamTitle($streamingUrl, $interval, $offset = 0, $headers = true)
{
    $needle = 'StreamTitle=';
    $ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';

    $opts = [
            'http' => [
            'method' => 'GET',
            'header' => 'Icy-MetaData: 1',
            'user_agent' => $ua
        ]
    ];

    if (($headers = get_headers($streamingUrl))) {
        foreach ($headers as $h) {
            if (strpos(strtolower($h), 'icy-metaint') !== false && ($interval = explode(':', $h)[1])) {
                break;
            }
        }
    }

    $context = stream_context_create($opts);

    if ($stream = fopen($streamingUrl, 'r', false, $context)) {
        $buffer = stream_get_contents($stream, $interval, $offset);
        fclose($stream);

        if (strpos($buffer, $needle) !== false) {
            $title = explode($needle, $buffer)[1];
            return substr($title, 1, strpos($title, ';') - 2);
        } else {
            return getMp3StreamTitle($streamingUrl, $interval, $offset + $interval, false);
        }
    } else {
        throw new Exception("Unable to open stream [{$streamingUrl}]");
    }
}

var_dump(getMp3StreamTitle('http://str30.creacast.com/r101_thema6', 19200));

我希望这有帮助!

答案 2 :(得分:1)

非常感谢代码fra_casula。这是在PHP&lt; = 5.3(原始目标为5.4)上运行的略微简化的版本。它还重用了相同的连接资源。

由于我自己的需要,我删除了异常,如果找不到任何内容,则返回false。

    private function getMp3StreamTitle($steam_url)
    {
        $result = false;
        $icy_metaint = -1;
        $needle = 'StreamTitle=';
        $ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';

        $opts = array(
            'http' => array(
                'method' => 'GET',
                'header' => 'Icy-MetaData: 1',
                'user_agent' => $ua
            )
        );

        $default = stream_context_set_default($opts);

        $stream = fopen($steam_url, 'r');

        if($stream && ($meta_data = stream_get_meta_data($stream)) && isset($meta_data['wrapper_data'])){
            foreach ($meta_data['wrapper_data'] as $header){
                if (strpos(strtolower($header), 'icy-metaint') !== false){
                    $tmp = explode(":", $header);
                    $icy_metaint = trim($tmp[1]);
                    break;
                }
            }
        }

        if($icy_metaint != -1)
        {
            $buffer = stream_get_contents($stream, 300, $icy_metaint);

            if(strpos($buffer, $needle) !== false)
            {
                $title = explode($needle, $buffer);
                $title = trim($title[1]);
                $result = substr($title, 1, strpos($title, ';') - 2);
            }
        }

        if($stream)
            fclose($stream);                

        return $result;
    }

答案 3 :(得分:1)

这是使用HttpClient获取元数据的C#代码:

public async Task<string> GetMetaDataFromIceCastStream(string url)
    {
        m_httpClient.DefaultRequestHeaders.Add("Icy-MetaData", "1");
        var response = await m_httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        m_httpClient.DefaultRequestHeaders.Remove("Icy-MetaData");
        if (response.IsSuccessStatusCode)
        {
            IEnumerable<string> headerValues;
            if (response.Headers.TryGetValues("icy-metaint", out headerValues))
            {
                string metaIntString = headerValues.First();
                if (!string.IsNullOrEmpty(metaIntString))
                {
                    int metadataInterval = int.Parse(metaIntString);
                    byte[] buffer = new byte[metadataInterval];
                    using (var stream = await response.Content.ReadAsStreamAsync())
                    {
                        int numBytesRead = 0;
                        int numBytesToRead = metadataInterval;
                        do
                        {
                            int n = stream.Read(buffer, numBytesRead, 10);
                            numBytesRead += n;
                            numBytesToRead -= n;
                        } while (numBytesToRead > 0);

                        int lengthOfMetaData = stream.ReadByte();
                        int metaBytesToRead = lengthOfMetaData * 16;
                        byte[] metadataBytes = new byte[metaBytesToRead];
                        var bytesRead = await stream.ReadAsync(metadataBytes, 0, metaBytesToRead);
                        var metaDataString = System.Text.Encoding.UTF8.GetString(metadataBytes);
                        return metaDataString;
                    }
                }
            }
        }

        return null;
    }