乱斗西游魔悟空视频:提取 SWF 中的音频!! - -7℃→百一的生活 - 博客园

来源:百度文库 编辑:科学网 时间:2020/02/22 23:12:45

提取 SWF 中的音频!!

 

好久没有更新博客了, 原因有好多, 天气热人烦躁是一个方面, 最大原因是自己懒得写. 本篇博文其实一个月之前就计划写了, 但每次开了头就感觉没有必要写下去了. 不废话了切入正题.

从 SWF 中提取音频的起因是想下载国内某音乐推荐站的音乐, 但赖于此站点推荐的音乐基本都被转化成了 SWF 格式的文件形式, 所以突发奇想能不能把 SWF 的音频给提取出来. 经过资料的搜集和准备, 最后还是成功了一半, 虽然并不是所有 SWF 内的音频可以正确的提取出来, 但对 MP3 格式的音频提取已经做的很完美了.

首先找到的一个处理 SWF 的类库 SwfDotNet, 本类库支持读写 SWF 7.0 之前(包括)的文件, 官方网站: http://sourceforge.net/projects/swfdotnet/. 基于 GPL 协议的开源 .Net 类库, 本类库RC1 已经好长时间了, 两年已经没有人维护了, 虽然不是很完美不过已经足够了.

因为在提取的中途出了点小波折, 所以后来又找到了 SWF 的格式规范. 从 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 , 大概是自己对 格式规范>没有读好, 读懂.

 

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

{

    ///

    /// Swf 处理类

    ///

    internal static class SwfProcessUtility

    {

        ///

        /// 读取指定的 SWF 文件

        ///

        ///

        ///

        public static Swf ReadSwfFile(string swfFilePath)

        {

            SwfReader swfReader = new SwfReader(swfFilePath);

            return swfReader.ReadSwf();

        }

        ///

        /// 将 tags 内 SoundStreamBlock 合并到一个文件内导出

        ///

        ///

        ///

        ///

        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();

                        }

                    }

                }

            }

        }

        ///

        /// 将 DefineSoundTag 中的音频导出

        /// 实际上只对MP3格式Sound有作用

        ///

        ///

        ///

        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是咋的处理的, 可能需要仔细分析. 为啥每个 SoundData 偏移之后, 导出的文件就可以使用.

大致就这么多, 希望能给看到的朋友以帮助. 这里提供源代码下载. 开发环境 VS2008, .NET 2.0 .

下载: [http://ishare.iask.sina.com.cn/cgi-bin/fileid.cgi?fileid=4312000]

 

0 0 0(请您对文章做出评价)posted @ 2008-09-03 12:22 青岛小帅哥 阅读(2135) 评论(5)  编辑 收藏 网摘 所属分类: flash类 swf文件 1306922#1楼巫云       在2008-09-03 12:44说: 我也是青岛的哦!
认识一下,^_^
  回复  引用  查看     #2楼Justin       在2008-09-03 12:55说: lz这方面很有研究!
  回复  引用  查看     #3楼testsssssssss[未注册用户] 在2008-09-03 13:02说: 这个文章应该是转载的,我看过此篇文章内容完全一样
  回复  引用     #4楼nasa       在2008-09-03 13:24说: --引用--------------------------------------------------
testsssssssss: 这个文章应该是转载的,我看过此篇文章内容完全一样
--------------------------------------------------------
我看到过 是这个
http://zhq.ahau.edu.cn/blog/article/452.htm