提取 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 格式的音频大致的存放规律为:

  1. SoundStreamBlock TAG 的 SoundData 字段存放一个 MP3STREAMSOUNDDATA 结构的数据;
  2. MP3STREAMSOUNDDATA 拥有一个 UI16 格式的 SampleCount 字段;
  3. MP3STREAMSOUNDDATA 第二个字段 Mp3SoundData 存放具体的音频, 其数据结构为 MP3SOUNDDATA;
  4. MP3SOUNDDATA 的结构有两个字段, 第一个为 SI16 格式的 SeekSamples, 第二个为 Mp3Frames.
  5. Mp3Frames 为实际音频格式的 MP3 的帧, 可以有 0 个或多个, 也就是一个数组;
  6. 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 .

下载:

 

为奥运健儿加油!

TUPUNCO

2008.08.19

好运每一天.




[本日志由 tupunco 于 2008-11-21 05:10 PM 编辑]
文章来自: 本站原创
引用通告: 查看所有引用 | 我要引用此文章
Tags: SWF FLASH .Net C# 音频 提取 音乐
评论: 0 | 引用: 0 | 查看次数: -
发表评论
昵 称:
密 码: 游客发言不需要密码.
内 容:
验证码: 验证码
选 项:
虽然发表评论不用注册,但是为了保护您的发言权,建议您注册帐号.