FLV文件格式说明

2011-01-26 14:29:07

<div class=&#34;tit&#34;>by <a href=&#34;http://hi.baidu.com/gaoxiansong/blog/item/b03d2d0789f65d72020881c0.html&#34;>http://hi.baidu.com/gaoxiansong/blog/item/b03d2d0789f65d72020881c0.html</a></div> <div class=&#34;tit&#34;>FLV文件格式分析</div> <div class=&#34;date&#34;>2009-07-15 01:41</div> <p>FLV是一个二进制文件,由文件头(FLV header)和很多tag组成。tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流(关键字或者文件信息之类)。</p> <p><strong>FLV Header</strong></p> <p>一般比较简单,包括文件类型之类的全局信息,如图:</p> <p><img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=193&#34; /></p> <table border=&#34;1&#34;> <tbody> <tr> <td>文件类型</td> <td>3bytes</td> <td>总是FLV(0x46 0x4C 0x56),否则...</td> </tr> <tr> <td>版本</td> <td>1byte</td> <td>一般是0x01,表示FLV version 1</td> </tr> <tr> <td>流信息</td> <td>1byte</td> <td>倒数第一bit是1表示有视频,倒数第三bit是1表示有音频,其他都应该是0(有些软件如flvtool2可能造成倒数第四bit是1,不过也没发现有什么不对)</td> </tr> <tr> <td>header长度</td> <td>4bytes</td> <td>整个文件头的长度,一般是9(3+1+1+4),有时候后面还有些别的信息,就不是9了</td> </tr> <tr> <td>&amp;nbsp;</td> <td>&amp;nbsp;</td> <td>&amp;nbsp;</td> </tr> </tbody> </table> <p><strong>FLV Body</strong></p> <p>FLV body就是由很多tag组成的。</p> <p>FLV文件里面帧的实体就是tag了。每个tag都可以分为两部分,第一部分包含是tag 类型信息,长度固定为15字节,如图:</p> <p><img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=194&#34; /></p> <p>第二部分为tag data,也就是flv的数据(有音频,视频,脚本等三类数据),根据不同的tag类型就有不同的数据区,数据区的长度由第一部分的数据区长度字段定义,如图:<img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=195&#34; /></p> <table border=&#34;1&#34; style=&#34;width: 613px; height: 256px;&#34;> <tbody> <tr> <td>previoustagsize</td> <td>4bytes</td> <td>前一个tag的长度,第一个tag就是0</td> </tr> <tr> <td>tag类型</td> <td>1byte</td> <td> <p>三类:</p> <p>.8 -- 音频tag</p> <p>.9 -- 视频tag</p> <p>.18 -- 脚本tag</p> </td> </tr> <tr> <td>数据区长度</td> <td>3bytes</td> <td>&amp;nbsp;</td> </tr> <tr> <td>时间戳</td> <td>3bytes</td> <td>单位毫秒,如果是脚本tag就是0</td> </tr> <tr> <td>扩展时间戳</td> <td>1byte</td> <td>作为时间戳的高位</td> </tr> <tr> <td>streamsID</td> <td>3bytes</td> <td>总是0(不知道干啥用)</td> </tr> <tr> <td>数据区</td> <td>&amp;nbsp;</td> <td>&amp;nbsp;</td> </tr> </tbody> </table> <p>接下来就是下一个tag的内容,其开始的四个字节定义了上个tag的总长度,注意上个tag的总长度中不包括上个tag之前的4个描述再上一个tag的长度的4个字节,如图:</p> <p><img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=196&#34; />接下来说一下文件尾,在文件尾的最后有四个字节是定义最后一个tag的长度的,如图:<img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=197&#34; /></p> <p>这里我门可以算一下,是00 00 00 DD是221,最后一个tag的长度是221,如图:<img alt=&#34;&#34; src=&#34;http://www.shareme.cn/blog/download.asp?id=198&#34; /></p> <p><strong>下面是不同类型的tag数据区的内容体</strong></p> <p><strong>Audio tag 数据区</strong></p> <p>&amp;nbsp;</p> <table border=&#34;1&#34; style=&#34;width: 619px; height: 475px;&#34;> <tbody> <tr> <td>audio信息</td> <td>1byte</td> <td> <p>前四位bits表示音频格式:</p> <p>0 -- 未压缩</p> <p>1 -- ADPCM</p> <p>2 -- MP3</p> <p>5 -- Nellymoser 8kHz momo</p> <p>6 -- Nellymoser</p> <p>下面两位bits表示samplerate:</p> <p>0 -- 5.5kHz</p> <p>1 -- 11kHz</p> <p>2 -- 22kHz</p> <p>3 -- 44kHz</p> <p>下面一位bit表示每个采样的长度:</p> <p>0 -- snd8Bit</p> <p>1 -- snd16Bit</p> <p>下面一位bit表示类型:</p> <p>0 -- sndMomo</p> <p>1 -- sndStereo</p> </td> </tr> <tr> <td>audio数据区</td> <td>不定</td> <td>&amp;nbsp;</td> </tr> </tbody> </table> <p><strong>video tag 数据区</strong></p> <p>&amp;nbsp;</p> <table border=&#34;1&#34; style=&#34;width: 615px; height: 280px;&#34;> <tbody> <tr> <td>video信息</td> <td>1byte</td> <td> <p>前四位bits表示类型:</p> <p>1 -- keyframe</p> <p>2 -- inner frame</p> <p>3 -- disposable inner frame (H.263 only)</p> <p>后四位bits表示编码器id:</p> <p>2 -- Seronson H.263</p> <p>3 -- Screen video</p> <p>4 -- On2 VP6</p> <p>5 -- On2 VP6 without channel</p> <p>6 -- Screen video version 2</p> </td> </tr> <tr> <td>video数据区</td> <td>不定</td> <td>&amp;nbsp;</td> </tr> </tbody> </table> <p><strong>script tag 数据区</strong></p> <p>略n字...</p> <p>&amp;nbsp;</p> <p>下面是自己写的一段根据上面对FLV文件结构的分析读取FLV播放时间的Delphi代码:</p> <p><br /> {$R *.dfm}<br /> procedure TForm1.Button1Click(Sender: TObject);<br /> begin<br /> OpenDialog1.Execute;<br /> Edit1.Text:=OpenDialog1.FileName;<br /> end;</p> <p>procedure TForm1.Button2Click(Sender: TObject);<br /> var<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iFileHandle:&amp;nbsp;&amp;nbsp; Integer;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iFileLength:&amp;nbsp;&amp;nbsp; Integer;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iBytesRead:&amp;nbsp;&amp;nbsp; Integer;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; Buffer:&amp;nbsp;&amp;nbsp; array&amp;nbsp;&amp;nbsp; of&amp;nbsp;&amp;nbsp; Byte;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; i:&amp;nbsp;&amp;nbsp; Integer;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; str1,str2:String;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; tminute,tSecond,tMillisecond,tmptime:Integer;<br /> begin<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; if Edit1.Text = &#39;&#39; then<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; begin<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ShowMessage(&#39;请选择文件!&#39;);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; exit;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; end;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iFileHandle&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; FileOpen( Edit1.Text ,&amp;nbsp;&amp;nbsp; fmOpenRead);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iFileLength&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; FileSeek(iFileHandle,&amp;nbsp;&amp;nbsp; 0,&amp;nbsp;&amp;nbsp; 2);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; FileSeek(iFileHandle,&amp;nbsp;&amp;nbsp; 0,&amp;nbsp;&amp;nbsp; 0);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; SetLength(Buffer,&amp;nbsp;&amp;nbsp; iFileLength);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; iBytesRead&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; FileRead(iFileHandle,&amp;nbsp;&amp;nbsp; Buffer[0],&amp;nbsp;&amp;nbsp; iFileLength);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; FileClose(iFileHandle);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; str1&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; &#39;&#39;;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; for&amp;nbsp;&amp;nbsp; i&amp;nbsp;&amp;nbsp; := iBytesRead&amp;nbsp;&amp;nbsp; -&amp;nbsp;&amp;nbsp; 4&amp;nbsp;&amp;nbsp; to&amp;nbsp;&amp;nbsp; iBytesRead&amp;nbsp;&amp;nbsp; -&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp; do<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; begin<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; str1&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; str1&amp;nbsp;&amp;nbsp; +&amp;nbsp;&amp;nbsp; IntToHex(Buffer[i],2);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; end;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; str2&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; &#39;&#39;;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; for&amp;nbsp;&amp;nbsp; i&amp;nbsp;&amp;nbsp; := iBytesRead&amp;nbsp;&amp;nbsp; -&amp;nbsp;&amp;nbsp; StrToInt(&#39;$&#39;+str1) to&amp;nbsp;&amp;nbsp; iBytesRead&amp;nbsp;&amp;nbsp; -&amp;nbsp;&amp;nbsp; (StrToInt(&#39;$&#39;+str1)-2)&amp;nbsp;&amp;nbsp; do<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; begin<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; str2&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; str2&amp;nbsp;&amp;nbsp; +&amp;nbsp;&amp;nbsp; IntToHex(Buffer[i],2);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; end;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; tMillisecond:=strtoint(&#39;$&#39;+str2);<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; tminute:=(tMillisecond div 1000) div 60;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; tSecond:=(tMillisecond div 1000) mod 60;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; tmptime:=tMillisecond mod 1000;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; Label2.Caption:=IntToStr(tminute)+&#39;分&#39;+IntToStr(tSecond)+&#39;秒&#39;;<br /> &amp;nbsp;&amp;nbsp;&amp;nbsp; Buffer&amp;nbsp;&amp;nbsp; :=&amp;nbsp;&amp;nbsp; nil;<br /> end;</p> <p>&amp;nbsp;</p>