是否可以使用PHP从音频流中提取音轨信息?我已经做了一些挖掘,我能找到的最接近的函数是stream_get_transports,但我的主机不支持通过fsockopen()的http传输,所以我将不得不做一些修补,看看该函数返回的是什么。
目前,我正在尝试从AOL流中提取艺术家和跟踪元数据。
答案 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;
}