提取 FLV 中的音频

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的基本结构为:

字段名称类型说明
TagTypeUI8

本TAG的类型. 可取值:

  • 8: audio
  • 9: video
  • 18: script data
  • 其他的: 保留
DataSizeUI24Data字段的数据长度
TimestampUI24无用?
TimestampExtendedUI8对上面的字段扩展之用
StreamIDUI24总是 0
Data
  • If TagType = 8
    AUDIODATA
  • If TagType = 9
    VIDEODATA
  • If TagType = 18
    SCRIPTDATAOBJECT
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结构为:

字段称类型说明
SoundFormatUB[4]

0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
14 = MP3 8-Khz
15 = Device-specific sound

SoundData 字段内音频的存储格式

格式7, 8, 14, 15 保留专用

格式10 (AAC) 只有在 Flash Player 9.0.115.0 及以上版本提供.

这里只关心 MP3 格式的, 因为其他编号的还不确定咋样处理.

SoundRateUB[2]
0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz

取样率 对AAC格式: 总为 3

SoundSize

UB[1]
0 = snd8Bit 8位
1 = snd16Bit 16位

每个取样的大小
对AAC格式: 总为 1

实际本字段为一个逻辑类型字段

SoundType

UB[1]
0 = sndMono 单声道
1 = sndStereo 立体声

标示是 立体或单声道的声音
对于Nellymoser格式: 总为 0
对于AAC格式: 总为 1

本字段也可以理解为逻辑类型字段

SoundDataUI8[sound data长度]

如果 SoundFormat 字段为 10
本数据为 AACAUDIODATA 类型的音频数据(本文忽略处理)
否则为实际的音频数据

对于MP3格式的音频数据直接将本字段的数据当成MP3数据来处理

SoundFormat/SoundRate/SoundSize/SoundType 加起来正好一个字节, 使得 SoundData 字段的数据大小为 FLVTAG 内 DataSize 字段的值减1.

前面已经指出"每一个FLVTAG 字段之后有一个UI32类型的PreviousTagSize" 其内的值正好为其前的FLVTAG长度, 这个部分应该校验之用.

从上面来算如果直接提取FLV的时候需要以下几步:

  1. 偏移9个字节
  2. 空一个3字节长度的UI32类型的PreviousTagSize0
  3. 读一个字节的UI8类型的TagType字段, 如果本字段为8处理音频数据, 其他的部分, 按下一步获得的DataSize值偏移制定的字节
  4. 再读3个字节的UI24类型的DataSize字段, 确定SoundData部分的数据大小
  5. 对于AUDIODATA 读取4Bit 判断是否为MP3类型的数据, 如果非MP3格式的也按着DataSize大小偏移忽略掉
  6. 偏移4位, 开始获取SoundData部分数据. SoundData的大小从第三步获得的DataSize来确定大小, 应该为 DataSize-1
  7. 偏移3个字节长度的UI32类型的PreviousTagSize部分
  8. 重复第三步

经过上面操作讲获得的全部的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

好运每一天.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值