<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>润物无声 &#187; mp4</title>
	<atom:link href="http://blog.zhourunsheng.com/tag/mp4/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhourunsheng.com</link>
	<description>天空一朵雨做的云</description>
	<lastBuildDate>Sat, 08 May 2021 05:17:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.41</generator>
	<item>
		<title>Android 流媒体播放之缓冲跳转实现</title>
		<link>http://blog.zhourunsheng.com/2012/05/android-%e6%b5%81%e5%aa%92%e4%bd%93%e6%92%ad%e6%94%be%e4%b9%8b%e7%bc%93%e5%86%b2%e8%b7%b3%e8%bd%ac%e5%ae%9e%e7%8e%b0/</link>
		<comments>http://blog.zhourunsheng.com/2012/05/android-%e6%b5%81%e5%aa%92%e4%bd%93%e6%92%ad%e6%94%be%e4%b9%8b%e7%bc%93%e5%86%b2%e8%b7%b3%e8%bd%ac%e5%ae%9e%e7%8e%b0/#comments</comments>
		<pubDate>Tue, 29 May 2012 06:06:09 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[移动开发]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[mp4]]></category>
		<category><![CDATA[流媒体]]></category>
		<category><![CDATA[视频]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1595</guid>
		<description><![CDATA[<p>前几篇文章分析了mp4文件的格式和文件的解析，以及视频边缓冲边播放的原理讲解与代码实现，具体可以参看Andro [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e6%b5%81%e5%aa%92%e4%bd%93%e6%92%ad%e6%94%be%e4%b9%8b%e7%bc%93%e5%86%b2%e8%b7%b3%e8%bd%ac%e5%ae%9e%e7%8e%b0/">Android 流媒体播放之缓冲跳转实现</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>前几篇文章分析了mp4文件的格式和文件的解析，以及视频边缓冲边播放的原理讲解与代码实现，具体可以参看<a href="http://blog.zhourunsheng.com/tag/android/">Android视频播放专题系列文章</a>的讲解，本文就展示一下缓冲跳转代码的实现原理。</p>
<p>先分享一下4幅图片，分别为播放前的缓存，正常播放中，跳转缓冲和跳转以后的正常播放。<br />
<img class="alignleft" src="http://blog.zhourunsheng.com/wp-content/uploads/2012/06/huanchongqian.png" alt="huanchongqian" width="160" height="240" /><br />
<img class="alignleft" src="http://blog.zhourunsheng.com/wp-content/uploads/2012/06/bofangzhong.png" alt="bofangzhong" width="160" height="240" /><br />
<img class="alignleft" src="http://blog.zhourunsheng.com/wp-content/uploads/2012/06/tiaozhuanghuanchong.png" alt="tiaozhuanghuanchong" width="160" height="240" /><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2012/06/tiaozhuangbofang.png" alt="tiaozhuangbofang" width="160" height="240" /><br />
<span id="more-1595"></span></p>
<h2>代码解析</h2>
<p><strong>视频断点分隔的数据结构定义</strong></p>
<p>定义了每一段视频的时间偏移点，文件位移偏移点，文件段的大小和当前的缓存状态</p>
<pre>class VideoInfo {
        double timestart;
        long offsetstart;
        long offsetend;
        long downloadsize;
        DownloadStatus status;

        public VideoInfo() {
            status = DownloadStatus.NOTSTART;
        }

        @Override
        public String toString() {
            String s = "beginTime:</pre>
<p><strong>缓存状态的定义</strong></p>
<p>分别为还未缓存，正在下载中和缓存完毕</p>
<pre>    enum DownloadStatus {
        NOTSTART, DOWNLOADING, FINISH,
    }</pre>
<p><strong>分段视频的下载</strong></p>
<p>利用了Http协议的 Range 属性，下载部分文件</p>
<pre>private void downloadbyvideoinfo(VideoInfo vi) throws IOException {
        System.out.println("download -&gt; " + vi.toString());

        URL url = new URL(this.url);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(3000);
        conn.setRequestProperty("Range", "bytes=" + vi.offsetstart + "-"
                + vi.offsetend);

        RandomAccessFile raf = new RandomAccessFile(new File(localFilePath),
                "rws");
        raf.seek(vi.offsetstart);

        InputStream in = conn.getInputStream();

        byte[] buf = new byte[1024 * 10];
        int len;
        vi.downloadsize = 0;
        while ((len = in.read(buf)) != -1) {
            raf.write(buf, 0, len);
            vi.downloadsize += len;
            Message msg = handler.obtainMessage();
            msg.what = MSG_DOWNLOADUPDATE;
            msg.obj = vi.offsetstart + vi.downloadsize;
            handler.sendMessage(msg);
        }

        in.close();
        raf.close();
    }</pre>
<p><strong>检测指定时间点的视频数据是否已经缓存完毕</strong></p>
<pre>    public boolean checkIsBuffered(long time) {
        int index = -1;

        for (VideoDownloder.VideoInfo tvi : vilists) {
            if (tvi.timestart &gt; time) {
                break;
            }

            index++;
        }

        if (index &lt; 0 || index &gt;= this.vilists.size()) {
            return true;
        }

        final VideoInfo vi = this.vilists.get(index);

        if (vi.status == DownloadStatus.FINISH) {
            return true;
        } else if (vi.status == DownloadStatus.NOTSTART) {
            return false;
        } else if (vi.status == DownloadStatus.DOWNLOADING) {
            return (vi.downloadsize * 100 / (vi.offsetend - vi.offsetstart)) &gt; ((time - vi.timestart) * 100 / SEP_SECOND);
        }

        return true;
    }</pre>
<p><strong>跳转后加载指定时间点的视频数据</strong></p>
<pre>    public synchronized void seekLoadVideo(long time) {
        int index = -1;

        for (VideoDownloder.VideoInfo tvi : vilists) {
            if (tvi.timestart &gt; time) {
                break;
            }
            index++;
        }

        if (index &lt; 0 || index &gt;= this.vilists.size()) {
            return;
        }

        final VideoInfo vi = this.vilists.get(index);

        if (vi.status == DownloadStatus.NOTSTART) {
            executorService.submit(new Runnable() {

                @Override
                public void run() {
                    try {
                        vi.status = DownloadStatus.DOWNLOADING;
                        downloadbyvideoinfo(vi);
                        vi.status = DownloadStatus.FINISH;
                    } catch (IOException e) {
                        vi.status = DownloadStatus.NOTSTART;
                        e.printStackTrace();
                    }
                }
            });
        }

        downloadvideoindex = index;
    }</pre>
<p><strong>视频分割数据结构的初始化</strong></p>
<p>目前以5s进行视频的分段处理</p>
<pre>    private static final int SEP_SECOND = 5;

    public void initVideoDownloder(final long startoffset, final long totalsize) {
        if (isinitok) {
            return;
        }

        this.executorService.submit(new Runnable() {

            @Override
            public void run() {
                IsoFile isoFile = null;
                try {
                    isoFile = new IsoFile(new RandomAccessFile(localFilePath,
                            "r").getChannel());
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (isoFile == null) {
                    return;
                }

                CareyMp4Parser cmp4p = new CareyMp4Parser(isoFile);
                cmp4p.printinfo();

                vilists.clear();

                VideoInfo vi = null;
                for (int i = 0; i &lt; cmp4p.syncSamples.length; i++) {
                    if (vi == null) {
                        vi = new VideoInfo();
                        vi.timestart = cmp4p.timeOfSyncSamples[i];
                        vi.offsetstart = cmp4p.syncSamplesOffset[i];
                    }

                    if (cmp4p.timeOfSyncSamples[i] &lt; (vilists.size() + 1)
                            * SEP_SECOND) {
                        continue;
                    }

                    vi.offsetend = cmp4p.syncSamplesOffset[i];
                    vilists.add(vi);
                    vi = null;
                    i--;
                }

                if (vi != null) {
                    vi.offsetend = totalsize;
                    vilists.add(vi);
                    vi = null;
                }

                isinitok = true;

                downloadvideo(startoffset);
            }
        });
    }</pre>
<p><strong>MP4视频头部数据的缓存</strong></p>
<pre>    private void prepareVideo() throws IOException {
        URL url = new URL(remoteUrl);
        HttpURLConnection httpConnection = (HttpURLConnection) url
                .openConnection();
        httpConnection.setConnectTimeout(3000);
        httpConnection.setRequestProperty("RANGE", "bytes=" + 0 + "-");

        InputStream is = httpConnection.getInputStream();

        videoTotalSize = httpConnection.getContentLength();
        if (videoTotalSize == -1) {
            return;
        }

        File cacheFile = new File(localUrl);

        if (!cacheFile.exists()) {
            cacheFile.getParentFile().mkdirs();
            cacheFile.createNewFile();
        }

        RandomAccessFile raf = new RandomAccessFile(cacheFile, "rws");
        raf.setLength(videoTotalSize);
        raf.seek(0);

        byte buf[] = new byte[10 * 1024];
        int size = 0;

        videoCacheSize = 0;
        int buffercnt = 0;
        while ((size = is.read(buf)) != -1 &amp;&amp; (!isready)) {
            try {
                raf.write(buf, 0, size);
                videoCacheSize += size;
            } catch (Exception e) {
                e.printStackTrace();
            }

            if (videoCacheSize &gt; READY_BUFF &amp;&amp; (buffercnt++ % 20 == 0)) {
                mHandler.sendEmptyMessage(CACHE_VIDEO_READY);
            }
        }

        if (!isready) {
            mHandler.sendEmptyMessage(CACHE_VIDEO_READY);
        }

        is.close();
        raf.close();
    }</pre>
<p><strong>主程序的状态控制</strong></p>
<p>主要就是首先缓冲mp4的文件头，然后进行解析和播放，初始化播放器控制界面，同时为了提高用户体验，动态监测当前播放的进度和当前缓存的进度，目前的做法是至少缓存了超前5s的数据流，即如果当前播放在00:02:05，则理论上缓存的数据已经在00:02:10之后了，如果没有达到这个状态，则暂停当前的视频播放，先进行一段缓存，然后再接着播放，这样就不会出现视频播放的过程中出现一卡一卡的情况。</p>
<pre>    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case VIDEO_STATE_UPDATE:
                int cachepercent = (int) (videoCacheSize * 100 / (videoTotalSize == 0 ? 1
                        : videoTotalSize));
                videoseekbar.setSecondaryProgress(cachepercent);

                int positon = mediaPlayer.getCurrentPosition();
                int duration = mediaPlayer.getDuration();
                int playpercent = positon * 100
                        / (duration == 0 ? 1 : duration);

                if (mediaPlayer.isPlaying()) {
                    playbtn.setImageResource(R.drawable.player_pad_button_pause_normal);

                    tvcurtime.setText(transtimetostr(positon));
                    tvtotaltime.setText(transtimetostr(duration));
                    videoseekbar.setProgress(playpercent);

                    int next2sec = positon + 2 * 1000;
                    if (next2sec &gt; duration) {
                        next2sec = duration;
                    }

                    if (!vdl.checkIsBuffered(next2sec / 1000)) {
                        mediaPlayer.pause();
                        showLoading();
                    }
                } else {
                    playbtn.setImageResource(R.drawable.player_pad_button_play_normal);

                    if (!userpaused &amp;&amp; isloading) {
                        int next5sec = positon + 5 * 1000;
                        if (next5sec &gt; duration) {
                            next5sec = duration;
                        }

                        if (vdl.checkIsBuffered(next5sec / 1000)) {
                            mediaPlayer.start();
                            hideLoading();
                        }
                    }
                }

                mHandler.sendEmptyMessageDelayed(VIDEO_STATE_UPDATE, 1000);
                break;

            case CACHE_VIDEO_READY:
                try {
                    mediaPlayer.setDataSource(localUrl);
                    mediaPlayer.prepareAsync();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;

            case VideoDownloder.MSG_DOWNLOADFINISH:
                videoCacheSize = videoTotalSize;
                break;

            case VideoDownloder.MSG_DOWNLOADUPDATE:
                if ((Long) msg.obj &gt; videoCacheSize) {
                    videoCacheSize = (Long) msg.obj;
                }
                break;
            }

            super.handleMessage(msg);
        }
    };</pre>
<p><strong>后续</strong></p>
<p>大体的代码流程就是这个样子,鉴于代码量比较大，就不全部贴出来了，相信大家整体浏览一遍也清楚了，源代码文件可以在<a title="BBVideoPlayer.java" href="http://carey-blog-image.googlecode.com/files/BBVideoPlayer%2820120529%29.java">点击这里</a>下载，如果还有相关的问题，请给我留言。</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e6%b5%81%e5%aa%92%e4%bd%93%e6%92%ad%e6%94%be%e4%b9%8b%e7%bc%93%e5%86%b2%e8%b7%b3%e8%bd%ac%e5%ae%9e%e7%8e%b0/">Android 流媒体播放之缓冲跳转实现</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2012/05/android-%e6%b5%81%e5%aa%92%e4%bd%93%e6%92%ad%e6%94%be%e4%b9%8b%e7%bc%93%e5%86%b2%e8%b7%b3%e8%bd%ac%e5%ae%9e%e7%8e%b0/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Android 视频播放之流媒体解析</title>
		<link>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e8%a7%a3%e6%9e%90/</link>
		<comments>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e8%a7%a3%e6%9e%90/#comments</comments>
		<pubDate>Tue, 22 May 2012 08:04:15 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[移动开发]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[mp4]]></category>
		<category><![CDATA[流媒体]]></category>
		<category><![CDATA[视频]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1567</guid>
		<description><![CDATA[<p>最近做的一个项目需求是要实现视频的边缓存边播放,还要能实现视频内部的跳转，实际来讲就是视频点播，只不过要通过h [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e8%a7%a3%e6%9e%90/">Android 视频播放之流媒体解析</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>最近做的一个项目需求是要实现视频的边缓存边播放,还要能实现视频内部的跳转，实际来讲就是视频点播，只不过要通过http协议来实现，也即用户可以拖动seekbar跳转到播放指定时间点的视频,要实现这样的功能，那么就需要提前解析视频的关键信息，得到时间点对应的视频流的内部偏移量，这样当进行实际跳转的时候就能根据跳转的时间点转换到文件内部的偏移量，进而进行视频流内容的缓存下载。</p>
<p>至于如何实现视频的边缓存边播放，可以参照我的博文《<a href="http://blog.zhourunsheng.com/2012/05/android%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e8%be%b9%e7%bc%93%e5%ad%98%e8%be%b9%e6%92%ad%e6%94%be/">Android视频播放之边缓存边播放</a>》和 《<a href="http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/">Android 视频播放之流媒体格式处理</a>》，本文的目的是给大家展示怎么样进行视频数据流的解析.<span id="more-1567"></span></p>
<h2>前期准备</h2>
<p>依赖的第三方解析包 mp4parser，Google的项目地址为<a href=" http://code.google.com/p/mp4parser/"> http://code.google.com/p/mp4parser/</a>,它主要实现了mp4和其他格式媒体数据的解析，本文的解析代码就是依赖于它底层的数据解析,项目中有一个图形化界面的解析器，下载地址为 <a href="http://mp4parser.googlecode.com/files/isoviewer-2.0-RC-7.jar">http://mp4parser.googlecode.com/files/isoviewer-2.0-RC-7.jar</a>，可以通过它来查看视频文件的整体编码结构。</p>
<p>通过在命令行执行</p>
<pre>F:\workspace\mp4parser-read-only&gt;java -jar isoviewer-2.0-RC-7.jar</pre>
<p>来启动程序，然后通过菜单的File导入需要解析的视频文件就好了</p>
<p>截图如下：<br />
<img class="alignnone" title="parse_mp4" src="http://blog.zhourunsheng.com/wp-content/uploads/2012/05/parse_mp4.png" alt="parse_mp4" width="600" height="400" /></p>
<p>本文代码依赖的jar包如下：</p>
<ol>
<li><a href="http://carey-blog-image.googlecode.com/files/aspectjrt.jar">http://carey-blog-image.googlecode.com/files/aspectjrt.jar</a></li>
<li><a href="http://carey-blog-image.googlecode.com/files/isoparser-1.0-RC-6-20120510.091755-1.jar">http://carey-blog-image.googlecode.com/files/isoparser-1.0-RC-6-20120510.091755-1.jar</a></li>
</ol>
<h2>关键代码解析</h2>
<p><strong>1. 计算视频的总时长</strong></p>
<p>视频的总时间轴 Duration 除以时间轴的比例 Timescale，结果就是当前视频的总秒数</p>
<pre>lengthInSeconds = (double) isoFile.getMovieBox().getMovieHeaderBox().getDuration() / isoFile.getMovieBox().getMovieHeaderBox().getTimescale();</pre>
<p><strong>2. 获取音频和视频的box数据</strong></p>
<p>一般的流媒体数据有两个TrackBox，第一个为音频，第二个为视频</p>
<pre>List&lt;TrackBox&gt; trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);</pre>
<p><strong>3. 解析视频的box数据</strong></p>
<p>取得关键帧的信息，因为视频的跳转只能在关键帧之间跳转，所以我们需要重点关注关键帧对应的时间点</p>
<pre>Iterator&lt;TrackBox&gt; iterator = trackBoxes.iterator();

SampleTableBox stbl = null;
TrackBox trackBox = null;
while (iterator.hasNext()) {
  trackBox = iterator.next();
  stbl = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox();
  // 我们只处理视频的关键帧，音频数据略过
  if (stbl.getSyncSampleBox() != null) {
    syncSamples = stbl.getSyncSampleBox().getSampleNumber();
    syncSamplesSize = new long[syncSamples.length];
    syncSamplesOffset = new long[syncSamples.length];
    timeOfSyncSamples = new double[syncSamples.length];
    break;
  }
}</pre>
<p><strong>4. 获取视频box中内部的详细参数</strong></p>
<p>包括帧与时间的对应关系，即每一帧对应视频的时间点是第几秒，帧与chunk的对应关系，mp4为了进行视频的压缩，都是把一系列连续的帧放到一个chunk中，每个chunk中帧的数量不等，chunk与数据流文件偏移的关系，即每个chunk对应的视频流数据在文件的具体偏移位置。</p>
<pre>// first we get all sample from the 'normal' MP4 part. if there are none - no problem.
SampleSizeBox sampleSizeBox = trackBox.getSampleTableBox().getSampleSizeBox();
ChunkOffsetBox chunkOffsetBox = trackBox.getSampleTableBox().getChunkOffsetBox();
SampleToChunkBox sampleToChunkBox = trackBox.getSampleTableBox().getSampleToChunkBox();</pre>
<p><strong>5. 计算偏移量 </strong></p>
<p>根据视频的上述信息计算每一个帧对应的具体时间点，以及其数据流在文件中的偏移量</p>
<pre>if (sampleToChunkBox != null
&amp;&amp; sampleToChunkBox.getEntries().size() &gt; 0
&amp;&amp; chunkOffsetBox != null
&amp;&amp; chunkOffsetBox.getChunkOffsets().length &gt; 0
&amp;&amp; sampleSizeBox != null &amp;&amp; sampleSizeBox.getSampleCount() &gt; 0) {
long[] numberOfSamplesInChunk = sampleToChunkBox.blowup(chunkOffsetBox.getChunkOffsets().length);
if (sampleSizeBox.getSampleSize() &gt; 0) {
// Every sample has the same size! no need to store each size separately
// this happens when people use raw audio formats in MP4 (are you stupid guys???)
int sampleIndex = 0;
long sampleSize = sampleSizeBox.getSampleSize();
for (int i = 0; i &lt; numberOfSamplesInChunk.length; i++) {
long thisChunksNumberOfSamples = numberOfSamplesInChunk[i];
long sampleOffset = chunkOffsetBox.getChunkOffsets()[i];
for (int j = 0; j &lt; thisChunksNumberOfSamples; j++) {
   // samples always start with 1 but we start with zero
   // therefore +1
   int syncSamplesIndex = Arrays.binarySearch(syncSamples,sampleIndex + 1);
   if (syncSamplesIndex &gt;= 0) {
      syncSamplesOffset[syncSamplesIndex] = sampleOffset;
      syncSamplesSize[syncSamplesIndex] = sampleSize;
   }
  sampleOffset += sampleSize;
  sampleIndex++;
}
}
} else {
// the normal case where all samples have different sizes
int sampleIndex = 0;
long sampleSizes[] = sampleSizeBox.getSampleSizes();
for (int i = 0; i &lt; numberOfSamplesInChunk.length; i++) {
   long thisChunksNumberOfSamples = numberOfSamplesInChunk[i];
   long sampleOffset = chunkOffsetBox.getChunkOffsets()[i];
   for (int j = 0; j &lt; thisChunksNumberOfSamples; j++) {
      long sampleSize = sampleSizes[sampleIndex];
      // samples always start with 1 but we start with zero therefore +1
      int syncSamplesIndex = Arrays.binarySearch(syncSamples, sampleIndex + 1);
      if (syncSamplesIndex &gt;= 0) {
         syncSamplesOffset[syncSamplesIndex] = sampleOffset;
         syncSamplesSize[syncSamplesIndex] = sampleSize;
      }
      sampleOffset += sampleSize;
      sampleIndex++;
   }
}
}

MediaHeaderBox mdhd = trackBox.getMediaBox().getMediaHeaderBox();
TrackHeaderBox tkhd = trackBox.getTrackHeaderBox();

trackMetaData.setTrackId(tkhd.getTrackId());
trackMetaData.setCreationTime(DateHelper.convert(mdhd.getCreationTime()));
trackMetaData.setLanguage(mdhd.getLanguage());
trackMetaData.setModificationTime(DateHelper.convert(mdhd.getModificationTime()));
trackMetaData.setTimescale(mdhd.getTimescale());
trackMetaData.setHeight(tkhd.getHeight());
trackMetaData.setWidth(tkhd.getWidth());
trackMetaData.setLayer(tkhd.getLayer());

List&lt;TimeToSampleBox.Entry&gt; decodingTimeEntries = null;
if (trackBox.getParent().getBoxes(MovieExtendsBox.class).size() &gt; 0) {
    decodingTimeEntries = new LinkedList&lt;TimeToSampleBox.Entry&gt;();
    for (MovieFragmentBox movieFragmentBox : trackBox.getIsoFile().getBoxes(MovieFragmentBox.class)) {
     List&lt;TrackFragmentBox&gt; trafs = movieFragmentBox.getBoxes(TrackFragmentBox.class);
       for (TrackFragmentBox traf : trafs) {
        if (traf.getTrackFragmentHeaderBox().getTrackId() == trackBox.getTrackHeaderBox().getTrackId()) {
          List&lt;TrackRunBox&gt; truns = traf.getBoxes(TrackRunBox.class);
          for (TrackRunBox trun : truns) {
             for (TrackRunBox.Entry entry : trun.getEntries()) {
               if (trun.isSampleDurationPresent()) {
                 if (decodingTimeEntries.size() == 0 || decodingTimeEntries.get(decodingTimeEntries.size() - 1).getDelta() != entry.getSampleDuration()) {
                    decodingTimeEntries.add(new TimeToSampleBox.Entry(1,entry.getSampleDuration()));
                 } else {
                    TimeToSampleBox.Entry e = decodingTimeEntries.get(decodingTimeEntries.size() - 1);
                    e.setCount(e.getCount() + 1);
               }
            }
         }
      }
    }
  }
}
} else {
    decodingTimeEntries = stbl.getTimeToSampleBox().getEntries();
}

long currentSample = 0;
double currentTime = 0;
for (TimeToSampleBox.Entry entry : decodingTimeEntries) {
   for (int j = 0; j &lt; entry.getCount(); j++) {
      int syncSamplesIndex = Arrays.binarySearch(syncSamples,currentSample + 1);
      if (syncSamplesIndex &gt;= 0) {
        // samples always start with 1 but we start with zero therefore +1
        timeOfSyncSamples[syncSamplesIndex] = currentTime;
   }
  currentTime += (double) entry.getDelta() / (double) trackMetaData.getTimescale();
  currentSample++;
 }
}
}</pre>
<p><strong>6. 在android中运行</strong></p>
<pre>public void printinfo() {
System.out.println("视频总时长(秒): " + lengthInSeconds);
System.out.println("关键帧 \t 帧偏移 \t 帧大小 \t 帧对应的时间");

int size = syncSamples.length;

for (int i = 0; i &lt; size; i++) {
   System.out.println(syncSamples[i] + " " + syncSamplesOffset[i] + " " + syncSamplesSize[i] + " " + timeOfSyncSamples[i]);
}
}</pre>
<p>解析的结果如下：</p>
<pre>05-22 15:21:03.750: I/System.out(5291): remoteUrl: http://bbfile.b0.upaiyun.com/data/videos/2/vid_20120510_090204.mp4
05-22 15:21:03.757: I/System.out(5291): localUrl: /mnt/sdcard/VideoCache/1337671263394.mp4
05-22 15:21:16.343: I/System.out(5291): 视频总时长(秒): 123.636
05-22 15:21:16.343: I/System.out(5291): 关键帧 帧偏移 帧大小 帧对应的时间
05-22 15:21:16.343: I/System.out(5291): 1 405032 1449 0.0
05-22 15:21:16.343: I/System.out(5291): 16 423325 3058 0.5002777777777777
05-22 15:21:16.351: I/System.out(5291): 31 441936 2886 1.0
05-22 15:21:16.351: I/System.out(5291): 46 467978 3575 1.4996777777777774
05-22 15:21:16.351: I/System.out(5291): 61 498237 5578 1.9993999999999998
05-22 15:21:16.351: I/System.out(5291): 76 562086 7654 2.4993444444444437
。。。。。。
05-22 15:21:16.585: I/System.out(5291): 3676 15952210 13153 122.43723333333335
05-22 15:21:16.585: I/System.out(5291): 3691 16050289 11484 122.93692222222225
05-22 15:21:16.585: I/System.out(5291): 3706 16131270 12718 123.4366666666667</pre>
<p>通过以上数据可以分析出：<br />
a. 视频的总时长为 123.636s<br />
b. 1s钟时长的视频大约有30个帧，其中有2个关键帧，就拿第1s的视频数据来说，第1和16帧为关键帧，<br />
其对应的时间点分别为0s和0.5s，帧对应的数据流在当前视频文件内部的偏移量分别为 405032<br />
和 423325，视频流数据的大小分别为1449和3058</p>
<p>源代码文件下载 <a href="http://carey-blog-image.googlecode.com/files/CareyMp4Parser%2820120522%29.java">http://carey-blog-image.googlecode.com/files/CareyMp4Parser%2820120522%29.java</a></p>
<p>好了，视频的解析部分就讲到这里，有什么问题可以给我留言，后续再更新跳转实现的代码部分.</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e8%a7%a3%e6%9e%90/">Android 视频播放之流媒体解析</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e8%a7%a3%e6%9e%90/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
<enclosure url="http://bbfile.b0.upaiyun.com/data/videos/2/vid_20120510_090204.mp4" length="16168052" type="video/mp4" />
		</item>
		<item>
		<title>Android 视频播放之流媒体格式处理</title>
		<link>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/</link>
		<comments>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/#comments</comments>
		<pubDate>Sat, 19 May 2012 08:13:59 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[移动开发]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[mp4]]></category>
		<category><![CDATA[流媒体]]></category>
		<category><![CDATA[视频]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1552</guid>
		<description><![CDATA[<p>格式编码 项目的需求要实现流媒体的播放，简言之就是视频可以支持边下载边播放。鉴于MP4/h.264编码的优势， [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/">Android 视频播放之流媒体格式处理</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<h2>格式编码</h2>
<p>项目的需求要实现流媒体的播放，简言之就是视频可以支持边下载边播放。鉴于MP4/h.264编码的优势，视频采用MP4文件格式存储，因为mp4只是一个存储的规范，像国内的优酷主要采用flv文件格式，其内部也是MP4编码，flv是 Adobe 支持的格式，这样的话，不论是在网站端，通过pc浏览器浏览，借助于<a href="http://get.adobe.com/flashplayer/">Flash Player</a>，还是手持设备，借助于<a href="http://www.ffmpeg.org/">ffmpeg</a>的解码库，都能实现视频的无缝播放，算是一个比较完美的解决方案。<span id="more-1552"></span></p>
<p>在实际视频的处理中，发现一个问题，利用Android手机自己拍摄录制的视频，可以完美支持边下载边播放，通过Iphone手机录制的视频，然后转码成mp4格式的视频就不能支持边下载边播放了，问题困扰了两三天，没办法，开始研究mp4的存储格式，最后终于找到了问题的所在。</p>
<p>了解mp4格式的应该知道，mp4是采用一种box数据结构来存储视频数据的，理想的情况是，box的顺序为ftyp，moov，free和mdat，通过moov的解析就能获取该视频的关键信息，比如播放时长，关键帧数，sample存储情况，在mdat中对应的编码数据偏移量等等。这样只要视频缓存完头部信息，即moov之后，就能知道当前视频的关键数据了，随着mdat的逐渐下载，就能完美实现视频的边缓存边播放，具体的实例教程请参见我的博文<a href="http://blog.zhourunsheng.com/2012/05/android%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e8%be%b9%e7%bc%93%e5%ad%98%e8%be%b9%e6%92%ad%e6%94%be/">《Android视频播放之边缓存边播放》</a>。</p>
<p>但是通过第三方软件的编码转换，有的就把moov放置到mdat之后了，这样只有把视频全部下载完成之后才能获取该视频的关键信息，才能开始播放，理论上也就没法实现视频的边缓存边播放，找到问题的关键点之后，就开始寻找解决办法，终于找到qt-faststart，它能解析原视频格式，并将moov信息尽可能的提前，这样就能及早解析和播放视频了。</p>
<h2>格式转换</h2>
<p>下载地址：</p>
<ul>
<li><a href="http://notboring.org/downloads/video/qt-faststart.zip">qt-faststart.exe binary for Windows</a></li>
<li><a href="https://github.com/danielgtaylor/qtfaststart">qt-faststart written in Python</a></li>
<li><a href="http://renaun.com/blog/code/qtindexswapper/">qtindexswapper written for Adobe AIR</a></li>
</ul>
<p>我使用的是window的客户端，使用命令如下：</p>
<pre>Usage: qt-faststart （转换前的视频文件） (转换后的视频文件)</pre>
<p><strong>参考文章</strong></p>
<ul>
<li><a href="http://www.stoimen.com/blog/2010/11/12/how-to-make-mp4-progressive-with-qt-faststart/">how-to-make-mp4-progressive-with-qt-faststart</a></li>
<li><a href="http://multimedia.cx/eggs/improving-qt-faststart/">improving-qt-faststart</a></li>
<li><a href="http://www.chinaunicom.com.cn/upload/1287474923316.pdf">移动流媒体协议综述</a></li>
</ul>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/">Android 视频播放之流媒体格式处理</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2012/05/android-%e8%a7%86%e9%a2%91%e6%92%ad%e6%94%be%e4%b9%8b%e6%b5%81%e5%aa%92%e4%bd%93%e6%a0%bc%e5%bc%8f%e5%a4%84%e7%90%86/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
