提取 SWF 中的音频!!
好久没有更新博客了, 原因有好多, 天气热人烦躁是一个方面, 最大原因是自己懒得写. 本篇博文其实一个月之前就计划写了, 但每次开了头就感觉没有必要写下去了. 不废话了切入正题.
从 SWF 中提取音频的起因是想下载国内某音乐推荐站的音乐, 但赖于此站点推荐的音乐基本都被转化成了 SWF 格式的文件形式, 所以突发奇想能不能把 SWF 的音频给提取出来. 经过资料的搜集和准备, 最后还是成功了一半, 虽然并不是所有 SWF 内的音频可以正确的提取出来, 但对 MP3 格式的音频提取已经做的很完美了.
首先找到的一个处理 SWF 的类库 SwfDotNet, 本类库支持读写 SWF 7.0 之前(包括)的文件, 官方网站: http://sourceforge.net/projects/swfdotnet/. 基于 GPL 协议的开源 .Net 类库, 本类库RC1 已经好长时间了, 两年已经没有人维护了, 虽然不是很完美不过已经足够了.
因为在提取的中途出了点小波折, 所以后来又找到了 SWF 的格式规范<SWF File Format Specification V9>. 从 Adobe 官方得到.
SWF 文件格式中音频可以分为两大类:
- Event sounds
- Streaming sounds
SWF 中的各个元素在其二进制文件中都是以TAG的形式存在的. 对于音频来说涉及到的TAG大概有以下几种:
- SoundStreamHead2 Tag
- SoundStreamHead Tag
- SoundStreamBlock Tag
- StartSound Tag
- StartSound2 Tag
- DefineSound Tag
其中只有 SoundStreamBlock Tag 和 DefineSound Tag 会包含实际的音频流, 其他的都是用来标识用的.
SoundStreamBlock TAG 存放 Streaming sounds 类型的音频. 在某个 SWF 文件时间轴内如果有 SoundStreamBlock 格式的TAG , 那么在第一个 SoundStreamBlock TAG 之前必须包括一个 SoundStreamHead2 TAG 或者 SoundStreamHead TAG, 只有这样才能标记其后的 SoundStreamBlock TAG 内的音频格式/记住回放格式/每个SoundStreamBlock TAG的平均取样数等信息.往往, 对于一个 SWF 文件有多个SoundStreamBlock TAG 组成一个组来共同存放一个音频文件.
SoundStreamHead TAG 内的StreamSoundCompression 字段标识其后的 SoundStreamBlock TAG 包含的音频格式, 可以存放以下音频格式:
1 = ADPCM
SWF 4 和以后版本:
2 = MP3
SoundStreamHead2 TAG中的StreamSoundCompression 字段标识的音频格式可以有:
0 = uncompressed
1 = ADPCM
SWF 4 或以后版本:
2 = MP3
3 = uncompressed little-endian
SWF 6 或以后版本:
6 = Nellymoser
SoundStreamBlock TAG 的 StreamSoundData 字段存放音频的实际数据流, 其音频数据流的存放方式依赖于其前的SoundStreamHead TAG或者SoundStreamHead2 TAG 的StreamSoundCompression 字段标识, 对于不同的音频格式其存放的规则不一样.
这里有一个小小的问题, 在实际测试的时候并没有发现用SoundStreamHead2 TAG 标识的SoundStreamBlock TAG , 大概是自己对 <SWF 格式规范>没有读好, 读懂.
Event sounds 格式的音频以 DefineSound 的TAG 形式存在, 并且一个 SWF 文件可以包含多个 DefineSound TAG. 每个 DefineSound TAG 存放一个单独的音频文件, 每个 DefineSound TAG 包含一个本 TAG 内存放音频的格式的属性(SoundFormat). DefineSound TAG 存放的音频格式有:
0 = uncompressed
1 = ADPCM
SWF 4 或以后版本:
2 = MP3
3 = uncompressed little-endian
SWF 6 或以后版本:
6 = Nellymoser
似乎和SoundStreamHead2 TAG 标识的一样. 测试发现在一个拥有DefineSound TAG 的SWF文件之前往往有一个SoundStreamHead2 TAG , 其后紧跟第一个DefineSound TAG. SwfDotNet 在处理 DefineSound TAG 的时候直接定义了一个 DecompileToFile 方法, 可以把其中的音频直接导出到一个文件.
StartSound Tag 和 StartSound2 Tag 跟在 DefineSound TAG 之后用来开始播放音频, 对实际的提取没有多大的帮助.
根据以上个规制:
- SoundStreamHeadTag 之后跟多个SoundStreamBlock TAG 包含一个音频文件;
- SoundStreamBlock TAG 的 SoundData 字段存放音频文件数据;
- DefineSound TAG 直接导出音频文件.
写的提取代码为以下:
//导出 Sound
if (_SwfFile.Tags != null)
{
foreach (BaseTag tag in _SwfFile.Tags)
{
//流方式的Sound
if (tag is SoundStreamHeadTag)
{
SwfProcessUtility.OutSoundStreamBlock((tag as SoundStreamHeadTag).StreamSoundCompression, outPath, _SwfFile.Tags);
//break;
}
//直接的声音 可以直接导出
if (tag is DefineSoundTag)
{
SwfProcessUtility.OutDefineSoundTag((tag as DefineSoundTag), outPath);
}
}
}
SwfProcessUtility 类中定义了多个静态方法来出来 TAG, 源代码为:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SwfDotNet.IO;
using SwfDotNet.IO.Tags;
namespace TUP.SwfProcessing.Sound
{
/// <summary>
/// Swf 处理类
/// </summary>
internal static class SwfProcessUtility
{
/// <summary>
/// 读取指定的 SWF 文件
/// </summary>
/// <param name="swfFilePath"></param>
/// <returns></returns>
public static Swf ReadSwfFile(string swfFilePath)
{
SwfReader swfReader = new SwfReader(swfFilePath);
return swfReader.ReadSwf();
}
/// <summary>
/// 将 tags 内 SoundStreamBlock 合并到一个文件内导出
/// </summary>
/// <param name="_StreamSoundCompression"></param>
/// <param name="outPath"></param>
/// <param name="tags"></param>
public static void OutSoundStreamBlock(uint _StreamSoundCompression, string outPath, BaseTagCollection tags)
{
string outFileFileName = "{0}.{1}";
//1 = ADPCM 2 = MP3
//MP3 Sound
if (_StreamSoundCompression == 2)
{
outFileFileName = string.Format(outFileFileName, Guid.NewGuid(), "mp3");
using (FileStream stream = new FileStream(Path.Combine(outPath, outFileFileName), FileMode.OpenOrCreate, FileAccess.Write))
{
foreach (BaseTag tag in tags)
{
if (tag is SoundStreamBlockTag)
{
byte[] buffer = null;
buffer = (tag as SoundStreamBlockTag).SoundData;
//偏移 4 位
//SampleCount UI16 2 byte
//SeekSamples SI16 2 byte
stream.Write(buffer, 4, buffer.Length - 4);
stream.Flush();
}
}
}
}
//ADPCM Sound
//此方法处理 的 Sound 不能使用
if (_StreamSoundCompression == 1)
{
outFileFileName = string.Format(outFileFileName, Guid.NewGuid(), "ADPCM");
using (FileStream stream = new FileStream(Path.Combine(outPath, outFileFileName), FileMode.OpenOrCreate, FileAccess.Write))
{
foreach (BaseTag tag in tags)
{
if (tag is SoundStreamBlockTag)
{
byte[] buffer = null;
buffer = (tag as SoundStreamBlockTag).SoundData;
stream.Write(buffer, 0, buffer.Length - 0);
stream.Flush();
}
}
}
}
}
/// <summary>
/// 将 DefineSoundTag 中的音频导出
/// 实际上只对MP3格式Sound有作用
/// </summary>
/// <param name="_DefineSoundTag"></param>
/// <param name="outPath"></param>
public static void OutDefineSoundTag(DefineSoundTag _DefineSoundTag, string outPath)
{
string outFileFileName = "{0}.{1}";
outFileFileName = string.Format(outFileFileName, Guid.NewGuid(), _DefineSoundTag.SoundFormat);
_DefineSoundTag.DecompileToFile(Path.Combine(outPath, outFileFileName));
}
}
}
上面的代码也不多解释了, 注释很清楚. 其实关键的是 SwfProcessUtility 方法中的两个静态方法. 对于 DefineSound TAG 中的音频只是将其直接导出到文件并以其类型作为扩展名. 而 OutSoundStreamBlock 方法在处理 SoundStreamBlock TAG 的时候对于MP3 格式的音频做了特殊处理, 原因需要说明一下, 这个也就是之前提到的波折.
SoundStreamHead TAG 标识了之后的音频格式, 其后的 SoundStreamBlock TAG 的 SoundData 字段会因为不同的音频格式做不同的存放处理. 对于 MP3 格式的音频大致的存放规律为:
- SoundStreamBlock TAG 的 SoundData 字段存放一个 MP3STREAMSOUNDDATA 结构的数据;
- MP3STREAMSOUNDDATA 拥有一个 UI16 格式的 SampleCount 字段;
- MP3STREAMSOUNDDATA 第二个字段 Mp3SoundData 存放具体的音频, 其数据结构为 MP3SOUNDDATA;
- MP3SOUNDDATA 的结构有两个字段, 第一个为 SI16 格式的 SeekSamples, 第二个为 Mp3Frames.
- Mp3Frames 为实际音频格式的 MP3 的帧, 可以有 0 个或多个, 也就是一个数组;
- UI16 和 SI16 格式都为 2 byte , 加起来 4 byte;
所以就有了代码内的:
stream.Write(buffer, 4, buffer.Length - 4);
对每一个 SoundStreamBlock TAG 的 SoundData 字段都偏移 4 个字节, 把之后的字节依次的写到一个输出文件中, 就可以导出一个MP3文件.
上面大致也就是思路和结果, 可以完美的导出 MP3 文件. 不管是 Event 类型的还是 Stream 类型的音频. 对于文章开始的音乐推荐站来说, 其 SWF 格式为 V5 版, 其内的音频大多是 Stream 类型的MP3 格式.
这个地方还有一个没有搞清楚的地方, 因为 MP3 文件内并不是只存放 MP3 的帧, 还有头信息, 不知道SWF是咋的处理的, 可能需要仔细分析<SWF 格式规范>. 为啥每个 SoundData 偏移之后, 导出的文件就可以使用.
大致就这么多, 希望能给看到的朋友以帮助. 这里提供源代码下载. 开发环境 VS2008, .NET 2.0 .
下载: [http://ishare.iask.sina.com.cn/cgi-bin/fileid.cgi?fileid=4312000]