http://zhq.ahau.edu.cn/blog/article/464.htm
好久没有写东西了, 八月份写过一篇<提取 SWF 中的音频>, 这篇写 FLV 中提取音频, 算是上篇的姊妹篇. 本篇在一个多月之前就开了头, 并且代码也写好了, 每次想完成本文都不了了之了. 哎, 变懒了.
这次使用的 FLV 操作组件还是 SwfDotNet, 不过此组件对 FLV 操作部分没有开发完毕, 使用的时候完善了一部分代码, 并修改了一部分错误, 最后总算提取音频部分成功.
修改 SwfDotNet 类库的时候参考了Adobe 官方的 <Video File Format Specification Version 9>. 这个格式文档不但包括 FLV 格式说明, 还包括 F4V 格式说明.
FLV 文件字节编码格式为 BigEndianUnicode , SwfDotNet 内已经把处理方法给封装好了, 但不知道使用.Net框架的附带默认编码处理方式是否可以, SwfDotNet 作者把框架内好多附带方法都重新实现了一边, 这个不清楚为啥.
FLV 文件结构主要由两部分组成, Header 部分与 TAG 组部分. Header 的长度和结构是固定的, 前三个字节为固定的FLV标志, 其他的表示当前FLV文件是否有音频或视频部分, 还有一些空白的字节, 总共为9个字节. TAG 组为由三类 FLVTAG 和UI32类型的PreviousTagSize 交替组成, PreviousTagSize 标识其前的FLVTAG的大小, FLVTAG的基本结构为:
字段名称 | 类型 | 说明 |
---|---|---|
TagType | UI8 | 本TAG的类型. 可取值:
|
DataSize | UI24 | Data字段的数据长度 |
Timestamp | UI24 | 无用? |
TimestampExtended | UI8 | 对上面的字段扩展之用 |
StreamID | UI24 | 总是 0 |
Data |
| TAG 数据字段, 主要的数据就在这个里面. 本字段的数据类型取决与TagType内的值 |
实际上只要关心三个字段:TagType/DataSize/Data. 从TagType来判断Data字段的类型, Data字段的大小从DataSize字段来确定. 对于音频格式来说只要处理TagType为8的Data字段数据, 其他的TagType按着DataSize的大小偏移, 忽略掉.
三类 FLVTAG 实际上就是上面结构中的 TagType 值:
- 8 AUDIO TAG 音频TAG (音频结构的TAG, 也是本文处理时所关心的. 本TAG类似与 SWF中的 DefineSound TAG)
- 9 VIDEO TAG 视频TAG (视频结构的TAG, 类似于SWF中的VideoFrame TAG. 此TAG实际上才是FLV的视频.)
- 18 SCRIPTDATAOBJECT TAG 脚本数据对象TAG(保存FLV部分元数据之用)
AUDIO TAG的Data结构为:
字段称 | 类型 | 说明 |
---|---|---|
SoundFormat | UB[4] 0 = Linear PCM, platform endian | SoundData 字段内音频的存储格式 格式7, 8, 14, 15 保留专用 格式10 (AAC) 只有在 Flash Player 9.0.115.0 及以上版本提供. 这里只关心 MP3 格式的, 因为其他编号的还不确定咋样处理. |
SoundRate | UB[2] 0 = 5.5-kHz 1 = 11-kHz 2 = 22-kHz 3 = 44-kHz | 取样率 对AAC格式: 总为 3 |
SoundSize | UB[1] | 每个取样的大小 实际本字段为一个逻辑类型字段 |
SoundType | UB[1] | 标示是 立体或单声道的声音 本字段也可以理解为逻辑类型字段 |
SoundData | UI8[sound data长度] | 如果 SoundFormat 字段为 10 对于MP3格式的音频数据直接将本字段的数据当成MP3数据来处理 |
SoundFormat/SoundRate/SoundSize/SoundType 加起来正好一个字节, 使得 SoundData 字段的数据大小为 FLVTAG 内 DataSize 字段的值减1.
前面已经指出"每一个FLVTAG 字段之后有一个UI32类型的PreviousTagSize" 其内的值正好为其前的FLVTAG长度, 这个部分应该校验之用.
从上面来算如果直接提取FLV的时候需要以下几步:
- 偏移9个字节
- 空一个3字节长度的UI32类型的PreviousTagSize0
- 读一个字节的UI8类型的TagType字段, 如果本字段为8处理音频数据, 其他的部分, 按下一步获得的DataSize值偏移制定的字节
- 再读3个字节的UI24类型的DataSize字段, 确定SoundData部分的数据大小
- 对于AUDIODATA 读取4Bit 判断是否为MP3类型的数据, 如果非MP3格式的也按着DataSize大小偏移忽略掉
- 偏移4位, 开始获取SoundData部分数据. SoundData的大小从第三步获得的DataSize来确定大小, 应该为 DataSize-1
- 偏移3个字节长度的UI32类型的PreviousTagSize部分
- 重复第三步
经过上面操作讲获得的全部的SoundData数据合并输出到一个MP3扩展名的文件. 不过本篇文章中没有使用上面的方法, 计划在下一篇中贴出实现代码.
下面是使用SwfDotNet 的实现代码:
/// <summary> /// 输出FLV音频部分 /// </summary> private void FlvOutSound() { string fileName = Path.GetFileName(flvFile); string outFileFileName = string.Format("{0}.{1}", fileName, "mp3"); //读取FLV文件 FlvReader flvReader = new FlvReader(flvFile, true); Flv flv = flvReader.ReadFlv(); using (FileStream stream = new FileStream(Path.Combine(outPath, outFileFileName), FileMode.OpenOrCreate, FileAccess.Write)) { foreach (FlvBaseTag tag in flv.Tags) { //只处理AudioTAG if (tag is FlvAudioTag) { FlvAudioTag audio = tag as FlvAudioTag; //输出到文件 stream.Write(audio.SoundData, 0, audio.SoundData.Length); } } } } /// <summary> /// 输入的FLV文件 /// </summary> string flvFile = null; /// <summary> /// 输出的MP3文件目录 /// </summary> string outPath = null;
SwfDotNet 类库 FlvReader.cs 内的处理错误部分纠正
/// <summary> /// Reads the tag. /// </summary> /// <returns></returns> private FlvBaseTag ReadTag() { FlvBaseTag resTag = null; //不读取, 只测试当前的Tag 类型 byte tagType = br.PeekByte(); switch (tagType) { case (byte) FlvTagCodeEnum.Audio: resTag = new FlvAudioTag(); break; case (byte)FlvTagCodeEnum.Video: //这个处理部分和FlvScriptTag内的处理一致, 只是简单的偏移 resTag = new FlvVideoTag(); break; case (byte)FlvTagCodeEnum.Script: default: resTag = new FlvScriptTag(); break; } resTag.ReadData(version, br); br.ReadUInt32(); return resTag; }
主要的代码就这么点, 好像有点头重脚轻. 不过功能是全部实现了. 全部完成了想说明一下FLV中提取音频的用处. 因为自己喜欢听相声, 在几个播客网站下载了好多的视频, 但大部分是FLV类型的, FLV的视频部分视乎是多余, 插上耳机听就可以了, 也不常看视频部分, 所以就有了本文的想法, 不过总算难度不大.
下一篇计划写关于"摆脱 SwfDotNet 类库提取音频"的实现代码部分.
本文主要参考: <Video File Format Specification Version 9>.
这里是代码. 下载地址:
天冷了, 注意保暖.
TUPUNCO
2008.11.19
好运每一天.