【发布时间】:2016-06-10 11:36:40
【问题描述】:
我需要创建一个独立于元数据的 mp3 文件散列(即,可以在重新标记后计算相同的散列)。如何将音频数据仅提取到内存中,而不通过解压缩器实际运行?
MAD 似乎是一个很好的起点 - http://www.underbit.com/products/mad/,但似乎并没有明显公开执行此操作的函数。
任何指针表示赞赏!
【问题讨论】:
我需要创建一个独立于元数据的 mp3 文件散列(即,可以在重新标记后计算相同的散列)。如何将音频数据仅提取到内存中,而不通过解压缩器实际运行?
MAD 似乎是一个很好的起点 - http://www.underbit.com/products/mad/,但似乎并没有明显公开执行此操作的函数。
任何指针表示赞赏!
【问题讨论】:
如何才能将音频数据仅提取到内存中,而不通过解压缩器实际运行?
您无法在不解压缩的情况下提取音频数据 - 它已被压缩!但是,如果您只想要原始压缩流,请继续阅读!
典型的 mp3 音频文件将分为几个部分:
[可能的元标记]
[可能是垃圾]
[可能的 XING/LAME 标签 [可能更多垃圾]]
[mp3 音频帧]
[可能的元标记]
可能的元标记: 大多数 mp3 音频文件的头部都会有一个 id3 标记。请注意,某些用户可能会使用不同的标记格式标记他们的 mp3 文件,例如 APE,因此您也需要考虑到这一点。
可能是垃圾: 一些 mp3 音频文件已被多次标记、重新标记和转换,元标记标头可能无法为您提供第一个音频帧的准确偏移量,作为先前标记的残余可以留下。 foobar2000 可以选择解决此问题。
可能的 XING/LAME 标签:这些包含在 mp3 音频帧中,但它们不包含实际音频。 madplay 有代码向您展示如何读取和解析这些帧。 XING/LAME 标头可能有帧数,因此值得解析这些标头。同样,如果该文件已通过许多不同的标记器和编辑器,则可能会在此处找到几个格式错误、无效的音频帧。
MP3 音频帧:实际压缩流,分为“帧”。每帧都以同步位模式 0xFFE 开始。
可能的元标记:在文件末尾找到更多元标记的情况并不少见。 id3v1、APE、歌词都可以在这里找到。
要查找音频帧偏移量,您需要解析任何元标记标头,然后开始查找同步位模式。您不能只从文件开头开始寻找同步模式,因为并非所有标记器都正确支持unsynchronization,因此元标记本身可能包含 0xFFE 模式。
获得第一个音频帧的偏移量后,您应该查看文件末尾并计算其中有多少非音频数据,以便知道何时停止解析音频。一旦有了音频数据开头的偏移量和音频数据结尾的偏移量,就可以通过哈希/校验和函数传递音频数据!
【讨论】:
您可以使用ffmpeg 直接通过复制模式访问音频内容。什么格式都没有关系,因为 API 将为您提供一个包含原始数据的容器(仅在复制模式下)。如果您有视频或想要处理解码后的音频数据,您还可以解复用和解码。
查看 ffmpeg 的示例以快速了解如何执行此操作。通过使用 ffmpeg,我的意思是不使用该工具,而是使用 c++/c 中的 libffmpeg(libavformat,libavcodec),尽管我认为您也可以使用 ffmpeg 工具从 cmdline 执行此操作,方法是将输出发送到 stdout 并将其通过管道传输到 md5sum 或等价的东西(如果你是 unix 用户,那就是)。
特殊情况“-acodec copy”告诉 ffmpeg 使用与解码相同的编解码器进行编码。换言之,不会发生音频转码。
【讨论】:
什么样的音频数据?原始解码的 PCM 流?单独的 MP3 帧?如果它是封装在 .wav 中的 MP3 怎么办?它仍然可以有 .mp3 扩展名,但有完整的 .wav 包装。
剥离 ID3v1 标记很简单 - 文件末尾只有 128 个字节。 ID3v2 有点难 - 它是可变长度的,并且添加到 MP3 的开头,您必须解析长度字段(这是 4 个字节,其中仅使用最低 7 位,为标签提供 28 位最大长度)。 .wav 包装器会更难——我不知道 .wav 强加为元数据的任何细节。
【讨论】:
我最近也需要解决这个问题(检测具有不同 ID3 标签的重复 mp3 文件)。最简单的做法是使用 ffmpeg 制作一份去掉所有 ID3 标签的 mp3 文件的副本,然后取一个 md5 总和。
【讨论】:
ffmpeg 可以单独计算音频文件的音频片段的 MD5 哈希,即无元数据。
用途:
ffmpeg -v -i $file -acodec copy -f md5 -
请注意,FLAC 已经将 MD5 哈希存储为元数据。
【讨论】:
我为一个带有无法处理标签的旧 mp3 播放器的 Linux 机器编写了这个简单的小 sn-p。剩下的只是 mp3 标头和数据(在标准输出上编码)。您可以将它用于您的 md5。
#include <fcntl.h>
#define DUMPTAGS
int main(int argc, char **argv){
unsigned char buf[4096];
int len,fd = open(argv[1],O_RDONLY);
while (len=read(fd,buf,10)){ // handle ID3v2 tags (maybe multiple)
if (buf[0]=='I' && buf[1]=='D' && buf[2]=='3'){
len=read(fd,buf,buf[9]|(buf[8] << 7)|(buf[7] << 14)|(buf[6] << 21));
#ifdef DUMPTAGS
write(2,buf,len);
#endif
} else break;
}
while (write(1,buf,len)){
unsigned char tag[3] = {'T','A','G'}, *end;
len=read(fd,buf,4096);
end=(unsigned char *)memmem(buf,len,&tag,3);
if (end){ //handle ID3v1 tag (should only be 1)
write(1,buf,end-buf);
#ifdef DUMPTAGS
write(2,end,len-(end-buf));
#endif
break;
}
}
}
【讨论】: