热门:网页模板.net视频教程JQueryMVCjsonExtJs源码示例三级联动JQuery菜单
您现在的位置:.Net中文社区>> Silverlight>>正文内容

在 Silverlight 应用程序中实现对 FLV 视频格式的支持【附源码下载】

发布时间:2010年05月30日点击数: 佚名

 

Silverlight 有没有对 FLV 视频提供支持?【源码下载

好吧,所有的开发人员都是懒惰的,ME2。先查查微软的文档吧,FLV 视频是如此的普及,没准儿微软已经在 Silverlight 中提供了对 FLV 视频的支持。

结果,微软在 Silverlight 的文档中表示不支 FLV 格式,也不支持 VP6 和 H.263 编码的视频。

嘛,这样的结果已经提前预见到了。既然官方不支持,那就自己山寨一个吧。

 

如何才能让 Silverlight 支持非官方的媒体格式?

在 Silverlight 应用程序中播放媒体必须要用到的是 MediaElement 控件。为 MediaElement 控件设置媒体源的方法有两种:

  1. 为 Source 属性设置一个 Uri 实例;
  2. 使用 SetSource 方法设置一个 Stream 或 MediaStreamSource 实例。

Uri 和 Stream 的用法我知道,行不通。剩下的就只有 MediaStreamSource 了,查查怎么用吧。

没错,就是在这个时候,我做了这篇文章开头提到的事情:

我相信我搜遍了整个互联网,但所有的搜索结果都指向了同一个链接:ManagedMediaHelpers

 

那么 ManagedMediaHelpers 是个什么东东?简单说就是:

  1. 实现了一个 MP3 文件的分离器,用于将 MP3 文件中的音频流数据分离出来;
  2. 实现了一个继承自 MediaStreamSource 的 Mp3MediaStreamSource,把从 MP3 文件中分离出来的音频流交给 MediaElement 解码。

根据之前研究 DirectShow 的经验,要让 Silverlight 支持非官方的媒体格式,我得出了以下结论:

  1. 需要一个将音视频流从文件中分离出来的分离器(Parser);
  2. 需要一个将音频流解码成 PCM 的音频解码器(Audio Decoder)除非 Silverlight 已经内置;
  3. 需要一个将视频流解码成 YV12 的视频解码器(Vidoe Decoder)除非 Silverlight 已经内置;
  4. 将音视频的帧数据写入 MediaStreamSample 通过 MediaStreamSource 交给 MediaElement 呈现(Renderer)。

如何实现 FLV 视频的分离器?

所有点击这篇文章标题进来的人应该都知道 FLV 是 Adobe 定义的一个媒体文件格式。youtube、nicovidoe等在线视频网站(好吧,还有优酷和土豆)都使用这种文件格式作为媒体内容的容器,因为它能在 Flash 应用程序中播放。 

要实现 FLV 视频的分离器,就要知道 FLV 的文件格式定义。既然是 Adobe 定义的,那就在 Adobe 上找找吧。在 Google 中敲入 flv site:adobe.com 可以在搜索结果中找到一个名为 Video File Format Specification Version 9 的 PDF 文件。天杀的 Adobe !这个文档中存在两位数的错误,直接导致我浪费了几个小时的时间。下载 Video File Format Specification Version 10 这个文档,错误比较少。第10页中 CompositionTime 的 Type 是 UI24,而不是 SI24。

 

根据这个文档中的定义就可以写出一个 FLV 文件的分离器了。FLV 文件格式的定义比较简单,如果有时间,我会在另一篇文章里进行介绍。

ClassDiagram1

如何实现 FLV 视频的解码器?

我对看到这里的朋友说声抱歉。我没能实现对 VP6 和 H.263 视频的解码,所以我这篇文章的标题才叫《在 Silverlight 应用程序中实现对 FLV 视频格式的支持》。

要实现对 VP6 和 H.263 视频的解码,可以将 FFMpage 工程组的代码改写成 C# 的版本(泪目ing,逃~)。

就算要改写解码器,那也要有个参考吧。哼哼,本人号称搜遍互联网可不是吹的,我推荐你参考 DirectShow for Silverlight 的代码,作者已经实现了在 Silverlight 中播放 Ogg 音频流的解码器。

 

既然没能实现自己解码器,那就继续用 Silverlight 内置的解码器吧。

FLV 支持 AAC 音频和 H.264 视频流,而 Silverlight 则内置了 AAC 和 H.264 解码器。所以还是可以在 Silverlight 中播放 FLV 视频的,只不过必须是音频流使用 AAC 编码,视频流使用 H.264 编码的 FLV 格式的文件。

 

顺便说一句,土豆网的高清黑豆就是这种格式,并且新浪播客也支持上传这种格式(虽然官方说法是不支持)。

 

如何解码 FLV 文件中的 AAC 音频流?

要想 Silverlight 对 AAC 音频流进行解码,必须先让 Silverlight 知道用哪个解码器才能对 AAC 音频流进行解码。

下面的代码完成了这个工作,其中 MediaStreamAttributeKeys.CodecPrivateData 是重点所在,它是通过计算 WaveFormatExtensible 结构得到的。

WaveFormatExtensible 结构的值来自于 AUDIOTAG 的 AUDIODATA 结构(见第6页)。下面的代码为了方便说明,直接进行了赋值。

 

  1. protected override void OpenMediaAsync() 
  2.     WaveFormatExtensible wfx = new WaveFormatExtensible(); 
  3.     wfx.FormatTag = 0x00FF; // AAC=0x00FF; MP3=0x0055; ADPCM=0x5346; PCM=0x0001 
  4.     wfx.Channels = 2; 
  5.     wfx.BlockAlign = 8; 
  6.     wfx.BitsPerSample = 16; 
  7.     wfx.SamplesPerSec = 44100; 
  8.     wfx.AverageBytesPerSecond = wfx.SamplesPerSec * wfx.Channels * wfx.BitsPerSample / wfx.BlockAlign; 
  9.     wfx.Size = 0; 
  10.     string codecPrivateData = wfx.ToHexString(); 
  11.  
  12.     Dictionary<MediaStreamAttributeKeys, string> audioStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>(); 
  13.     audioStreamAttributes[MediaStreamAttributeKeys.CodecPrivateData] = codecPrivateData; // 管线需要正确初始化和呈现的编码解码器数据。对于视频,这是标头信息。对于音频,这是 base16 编码的 WaveFormatEx 结构。 
  14.     this.audioStreamDescription = new MediaStreamDescription(MediaStreamType.Audio, audioStreamAttributes); // 详尽描述媒体流,以便初始化 MediaElement 和基础媒体管线。 
  15.  
  16.     Dictionary<MediaSourceAttributesKeys, string> mediaSourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>(); 
  17.     mediaSourceAttributes[MediaSourceAttributesKeys.Duration] = "0"// 源的整数形式的播放时间长度,以 100 纳秒为增量单位(TimeSpan 结构的值的计时周期数)。 
  18.  
  19.     List<MediaStreamDescription> mediaStreamDescriptions = new List<MediaStreamDescription>(); 
  20.     mediaStreamDescriptions.Add(this.audioStreamDescription); 
  21.  
  22.     this.ReportOpenMediaCompleted(mediaSourceAttributes, mediaStreamDescriptions); // 向 MediaStreamSource 附加到的 MediaElement 报告媒体已打开并且 MediaStreamSource 具有其所包含的内容流的说明。 

 

之后 Silverlight 会调用 GetSampleAsync 方法获得 AAC 音频流的 MediaStreamSample 实例。实例化 AAC 音频流的 MediaStreamSample 很简单,只要将 FLV 文件中 AUDIOTAG 的 AUDIODATA 的 AACAUDIODATA 的 Data(Raw AAC frame)交给 MediaStreamSample 就行了。


注意!FLV 文件中第一个 AUDIOTAG 的 AUDIODATA 的 AACAUDIODATA 的 Data 总是 AudioSpecificConfig(在 ISO/IEC 14496-3 中定义),解码的时候注意跳过这个 AUDIOTAG。


如何解码 FLV 文件中的 H.264 视频流?

接下来是这篇文章最重要的部分,我将展示如何用 MediaStreamSource 解码 H.264 视频流。在此之前,你不会在互联网上获得任何关于如何使用 MediaStreamSource 解码视频的文字或代码。

下面的代码中不包括 MediaStreamAttributeKeys.CodecPrivateData 的设置,那是因为 H.264 视频流是不需要设置 MediaStreamAttributeKeys.CodecPrivateData 的。而 MediaStreamAttributeKeys.Width 和 MediaStreamAttributeKeys.Height 也不是需要设置的。为什么不需要设置?请继续往下读。

 

  1. protected override void OpenMediaAsync() 
  2.     Dictionary<MediaStreamAttributeKeys, string> videoStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>(); 
  3.     videoStreamAttributes[MediaStreamAttributeKeys.VideoFourCC] = "H264"// 实例化视频编码解码器所需的数据。这是一个由四个字符构成的值,也称作 FourCC。 
  4.     this.videoStreamDescription = new MediaStreamDescription(MediaStreamType.Video, videoStreamAttributes); 
  5.  
  6.     Dictionary<MediaSourceAttributesKeys, string> mediaSourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>(); 
  7.     mediaSourceAttributes[MediaSourceAttributesKeys.Duration] = "0"
  8.  
  9.     List<MediaStreamDescription> mediaStreamDescriptions = new List<MediaStreamDescription>(); 
  10.     mediaStreamDescriptions.Add(this.videoStreamDescription); 
  11.  
  12.     this.ReportOpenMediaCompleted(mediaSourceAttributes, mediaStreamDescriptions); 

 

之后 Silverlight 会调用 GetSampleAsync 方法获得 H.264 视频流的 MediaStreamSample 实例。实例化 H.264 的 MediaStreamSample 是比较麻烦的,要分成两个部分来说:

计算 AVCDecoderConfigurationRecord 得到 CodecPrivateData 数据(只有第一帧需要);
计算 NALUs 得到帧数据。
 

计算 AVCDecoderConfigurationRecord 得到 CodecPrivateData 数据

H.264 视频流的 CodecPrivateData 实际上就是 AVCDecoderConfigurationRecord 中 SequenceParameterSets(SPS)和 PictureParameterSets(PPS)使用 byte[] {00, 00, 01} 连接的字节数组。

注意!FLV 文件中第一个 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 总是 AVCDecoderConfigurationRecord(在 ISO/IEC 14496-15 中定义),解码的时候注意跳过这个 VIDOETAG。

AVCDecoderConfigurationRecord 结构的定义:

 

  1. aligned(8) class AVCDecoderConfigurationRecord {  
  2. unsigned int(8) configurationVersion = 1;  
  3. unsigned int(8) AVCProfileIndication;  
  4. unsigned int(8) profile_compatibility;  
  5. unsigned int(8) AVCLevelIndication;  
  6. bit(6) reserved = ‘111111’b;  
  7. unsigned int(2) lengthSizeMinusOne;  
  8. bit(3) reserved = ‘111’b;  
  9. unsigned int(5) numOfSequenceParameterSets;  
  10. for (i=0; i< numOfSequenceParameterSets; i++) {  
  11. unsigned int(16) sequenceParameterSetLength ;  
  12. bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;  
  13. }  
  14. unsigned int(8) numOfPictureParameterSets;  
  15. for (i=0; i< numOfPictureParameterSets; i++) {  
  16. unsigned int(16) pictureParameterSetLength;  
  17. bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;  
  18. }  

 

下面蓝色的部分就是 FLV 文件中的 AVCDecoderConfigurationRecord 部分。

 

  1. 00000130h: 00 00 00 17 00 00 00 00 01 4D 40 15 FF E1 00 0A ; .........M@.?.  
  2. 00000140h: 67 4D 40 15 96 53 01 00 4A 20 01 00 05 68 E9 23 ; gM@.朣..J ...h?  
  3. 00000150h: 88 00 00 00 00 2A 08 00 00 52 00 00 00 00 00 00 ; ?...*...R...... 

 

根据 AVCDecoderConfigurationRecord 结构的定义:

 

  1. configurationVersion = 01 
  2. AVCProfileIndication = 4D 
  3. profile_compatibility = 40 
  4. AVCLevelIndication = 15 
  5. lengthSizeMinusOne = FF <- 非常重要,是 H.264 视频中 NALU 的长度,计算方法是 1 + (lengthSizeMinusOne & 3) 
  6. numOfSequenceParameterSets = E1 <- SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F 
  7. sequenceParameterSetLength = 00 0A <- SPS 的长度 
  8. sequenceParameterSetNALUnits = 67 4D 40 15 96 53 01 00 4A 20 <- SPS 
  9. numOfPictureParameterSets = 01 <- PPS 的个数 
  10. pictureParameterSetLength = 00 05 <- PPS 的长度 
  11. pictureParameterSetNALUnits = 68 E9 23 88 00 <- PPS 

 

因此 CodecPrivateData 的字符串表示就是 000001674D4015965301004A2000000168E9238800

 

但是设置 MediaStreamAttributeKeys.CodecPrivateData 是没用的(只有 H.264 是这样,其他类型的视频流仍然需要设置),只有将 CodecPrivateData 写入 H.264 视频流第一帧数据的前面 Silverlight 才能正常解码。

也就是说,Silverlight 的 H.264 解码器会读取第一帧前面的 CodecPrivateData 数据来进行配置。

因为 CodecPrivateData 数据已经包含视频流的解码器参数(包括视频的宽高),所以就不需要设置 MediaStreamAttributeKeys.CodecPrivateData、MediaStreamAttributeKeys.Width 和 MediaStreamAttributeKeys.Height 了。

计算 NALU 得到帧数据

FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 不是原始视频帧数据,而是一个或更多个 NALU 数据片段。在这篇文章中,你认为 H.264 视频帧数据是由多个 NALU 组成的。当然实际上并不是这样,关于这部分的概念请自行 Google,本文将不做讨论。

下面是 FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 属性的数据(第一帧数据)。

红色的部分是 NALU 数据的长度,而红色部分的长度则由 lengthSizeMinusOne 决定。
蓝色的部分是 NALU 数据部分。
删除的部分是废弃的数据。

 

  1. 00000300h: 00 00 00 00 00 17 01 00 00 22 00 00 00 31 65 88 ; ........."...1e?  
  2. 00000310h: 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 F8 45 FB ; €@.窌Sg刲.?鳨?  
  3. 00000320h: F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 00 3F 2B ; ?q.づ,.......?+  
  4. 00000330h: 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 7A FE 00 ; [.WH)?.....衵?  
  5. 00000340h: 00 00 38 65 01 22 22 01 00 17 B7 95 53 67 FF 84 ; ..8e.""...窌Sg?  
  6. 00000350h: 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 2C 00 E8 ; l.?鳨.q.づ,.?  
  7. 00000360h: F3 37 75 43 90 00 00 03 00 15 EF AA A8 53 86 01 ; ?uC?....铼⊿?  
  8. 00000370h: DD 57 60 00 00 03 01 59 0C F4 3C 00 00 00 33 65 ; 軼`....Y.?...3e  
  9. 00000380h: 00 90 88 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 ; .悎€@.窌Sg刲.?  
  10. 00000390h: F8 45 FB F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 ; 鳨.q.づ,......  
  11. 000003a0h: 00 3F 2B 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 ; .?+[.WH)?.....?  
  12. 000003b0h: 7A FE 00 00 00 38 65 00 D8 88 80 40 05 B7 95 53 ; z?..8e.貓€@.窌S  
  13. 000003c0h: 67 FF 84 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 ; g刲.?鳨.q.づ  
  14. 000003d0h: 2C 00 E8 F3 37 75 43 90 00 00 03 00 15 EF AA A8 ; ,.梵7uC?....铼?  
  15. 000003e0h: 53 86 01 DD 57 60 00 00 03 01 59 0C F4 3C 00 00 ; S?軼`....Y.?..  
  16. 000003f0h: 00 F4 08 00 01 33 00 00 17 00 00 00 00 AF 01 27 ; .?..3.......?' 

 

帧数据是将多个 NALU 使用 byte[] {00, 00, 01} 连接的字节数组。

 

  1. byte[] = { 
  2.  
  3. 00,00,01,65,88,  
  4. 80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB,  
  5. F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B,  
  6. 5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE, 
  7.  
  8. 00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,  
  9. 6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8,  
  10. F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01,  
  11. DD,57,60,00,00,03,01,59,0C,F4,3C, 
  12.  
  13. 00,00,01,65,  
  14. 00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,  
  15. F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,  
  16. 00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,  
  17. 7A,FE, 
  18.  
  19. 00,00,01,65,00,D8,88,80,40,05,B7,95,53,  
  20. 67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,  
  21. 2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,  
  22. 53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C 
  23.  
  24. }; 

 

如果是第一帧数据,那么前面还要加上 CodecPrivateData 数据。

 

  1. byte[] = { 
  2.  
  3. 00,00,01,67,4D,40,15,96,53,01,00,4A,20, 
  4.  
  5. 00,00,01,68,E9,23,88,00, 
  6.  
  7. 00,00,01,65,88,  
  8. 80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB,  
  9. F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B,  
  10. 5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE, 
  11.  
  12. 00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,  
  13. 6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8,  
  14. F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01,  
  15. DD,57,60,00,00,03,01,59,0C,F4,3C, 
  16.  
  17. 00,00,01,65,  
  18. 00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,  
  19. F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,  
  20. 00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,  
  21. 7A,FE, 
  22.  
  23. 00,00,01,65,00,D8,88,80,40,05,B7,95,53,  
  24. 67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,  
  25. 2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,  
  26. 53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C 
  27.  
  28. }; 

 

可以用鼠标猛戳这里下载我的代码,我相信这是你可以在互联网上找到的唯一一个(2010年5月30日之后看到这篇文章的不算)用 MediaStreamSource 处理视频的示例。

我已经在项目中实现了 FlvParser 和 FlvMediaStreamSource,并且还实现了 Mp4Parser 和 Mp4MediaStreamSource,希望这些代码对你有帮助。

本站热点业务

更多模板/案例展示

关于我们 | 联系我们 | 团队日志 | 网站地图 | 网站合作