mp4缓冲播放

news/2024/10/18 2:36:08/

情景描述

    后台上传mp4文件到服务器,通过nginx代理静态资源MP4把访问地址参数提供给app。app用户反应,视频无法加载。或者加载过慢。(在chrome,firefox,360等众多浏览器直接访问mp4访问地址都是秒播)

mp4文件格式

moov对于MP4来说,就是目录,它是包含着mp4的元数据。moov模块存放着如下信息1 视频包括编码等级、分辨率、色域、码率、帧率、位深、时长等等……2 音频又包括声道、采样率等音频特有属性mdat:媒体数据容器。存放mp4大内容的地方。

加载快慢的原因是(元数据模块的顺序和使用的访问方式)

1 秒播:chrome,firefox,360 很智能,如果读取MP4文件,发现moov box不在文件前部,会直接读取MP4的文件尾部,加载moov box
2 卡顿:Flash如果读取MP4文件,发现moov box不在文件前部,不会直接读取MP4的文件尾部去寻找moov box,所以Flash要等文件全部下载完,取到文件尾部的moov头,才可以正常播放。
3 流媒体服务器,即使moov在文件尾部 也会先发moov头出去给CDN或用户,相当于CDN回源,或者用户回源请求到的MP4,已经是moov头在文件头部的了,虽然 这个时候 源站存储的还是moov头在文件尾部的MP4。

综上所述:所以,如果安卓采用和flash一样的机制来顺序解析mp4文件,如果mp4文件的moov模块在mdat后面。那么就会造成卡顿!!!

解决办法:

知道了mp4播放的原理,我们只要保证。moov在mdat前面解析就行了。ps:修改mp4文件大模块的顺序,是不会影响这个文件整体效果的。方法1:部署流媒体服务器,给安卓提供流媒体的播放地址。不再采用http地址访问MP4。(成本高,复杂,不考虑)
方法2:修改mp4文件的模块顺序。(修改mp4模块顺序的代码,可百度,nice)
方法3:安卓端修改解析mp4的顺序,先解析moov模块。(不懂安卓,不考虑)

附:修改mp4文件模块的代码(百度不到我也不会选择这个办法对吧 https://blog.csdn.net/lzh838330255/article/details/53286430)

  package cc.ligu.mvc.common;import java.io.*;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.channels.FileChannel;public class QtFastStart {public static boolean sDEBUG = false;private static void safeClose(Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (IOException e) {printe(e, "Failed to close file: ");}}}/* package */static long uint32ToLong(int int32) {return int32 & 0x00000000ffffffffL;}/*** Ensures passed uint32 value in long can be represented as Java int.*//* package */static int uint32ToInt(int uint32) throws UnsupportedFileException {if (uint32 < 0) {throw new UnsupportedFileException("uint32 value is too large");}return uint32;}/*** Ensures passed uint32 value in long can be represented as Java int.*//* package */static int uint32ToInt(long uint32) throws UnsupportedFileException {if (uint32 > Integer.MAX_VALUE || uint32 < 0) {throw new UnsupportedFileException("uint32 value is too large");}return (int) uint32;}/*** Ensures passed uint64 value can be represented as Java long.*//* package */static long uint64ToLong(long uint64) throws UnsupportedFileException {if (uint64 < 0) throw new UnsupportedFileException("uint64 value is too large");return uint64;}private static int fourCcToInt(byte[] byteArray) {return ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).getInt();}private static void printf(String format, Object... args) {if (sDEBUG) System.err.println("QtFastStart: " + String.format(format, args));}private static void printe(Throwable e, String format, Object... args) {printf(format, args);if (sDEBUG) e.printStackTrace();}private static boolean readAndFill(FileChannel infile, ByteBuffer buffer) throws IOException {buffer.clear();int size = infile.read(buffer);buffer.flip();return size == buffer.capacity();}private static boolean readAndFill(FileChannel infile, ByteBuffer buffer, long position) throws IOException {buffer.clear();int size = infile.read(buffer, position);buffer.flip();return size == buffer.capacity();}/* top level atoms */private static final int FREE_ATOM = fourCcToInt(new byte[]{'f', 'r', 'e', 'e'});private static final int JUNK_ATOM = fourCcToInt(new byte[]{'j', 'u', 'n', 'k'});private static final int MDAT_ATOM = fourCcToInt(new byte[]{'m', 'd', 'a', 't'});private static final int MOOV_ATOM = fourCcToInt(new byte[]{'m', 'o', 'o', 'v'});private static final int PNOT_ATOM = fourCcToInt(new byte[]{'p', 'n', 'o', 't'});private static final int SKIP_ATOM = fourCcToInt(new byte[]{'s', 'k', 'i', 'p'});private static final int WIDE_ATOM = fourCcToInt(new byte[]{'w', 'i', 'd', 'e'});private static final int PICT_ATOM = fourCcToInt(new byte[]{'P', 'I', 'C', 'T'});private static final int FTYP_ATOM = fourCcToInt(new byte[]{'f', 't', 'y', 'p'});private static final int UUID_ATOM = fourCcToInt(new byte[]{'u', 'u', 'i', 'd'});private static final int CMOV_ATOM = fourCcToInt(new byte[]{'c', 'm', 'o', 'v'});private static final int STCO_ATOM = fourCcToInt(new byte[]{'s', 't', 'c', 'o'});private static final int CO64_ATOM = fourCcToInt(new byte[]{'c', 'o', '6', '4'});private static final int ATOM_PREAMBLE_SIZE = 8;public static boolean fastStart(InputStream in, FileOutputStream out) throws IOException, MalformedFileException, UnsupportedFileException {boolean ret = false;FileInputStream inStream = (FileInputStream) in;FileChannel infile = null;FileChannel outfile= null;try {infile = inStream.getChannel();outfile = out.getChannel();return ret = fastStartImpl(infile, outfile);} finally {if (!ret) {infile.transferTo(0, infile.size(), outfile);//当转换不成功时(正常是因文件小),直接copy.//                out.delete();}else{//                in.delete();}safeClose(inStream);safeClose(out);}}/*** @param in  Input file.* @param out Output file.* @return false if input file is already fast start.* @throws IOException*/public static boolean fastStart(String in, String out) throws IOException, MalformedFileException, UnsupportedFileException {boolean ret = false;FileInputStream inStream = null;FileOutputStream outStream = null;FileChannel infile = null;FileChannel outfile= null;try {inStream = new FileInputStream(in);infile = inStream.getChannel();outStream = new FileOutputStream(out);outfile = outStream.getChannel();return ret = fastStartImpl(infile, outfile);} finally {if (!ret) {infile.transferTo(0, infile.size(), outfile);//当转换不成功时(正常是因文件小),直接copy.//                out.delete();}else{//                in.delete();}safeClose(inStream);safeClose(outStream);}}private static boolean fastStartImpl(FileChannel infile, FileChannel outfile) throws IOException, MalformedFileException, UnsupportedFileException {ByteBuffer atomBytes = ByteBuffer.allocate(ATOM_PREAMBLE_SIZE).order(ByteOrder.BIG_ENDIAN);int atomType = 0;long atomSize = 0; // uint64_tlong lastOffset;ByteBuffer moovAtom;ByteBuffer ftypAtom = null;// uint64_t, but assuming it is in int32 range. It is reasonable as int max is around 2GB. Such large moov is unlikely, yet unallocatable :).int moovAtomSize;long startOffset = 0;System.out.println("QtFastStart------"+"开始");// traverse through the atoms in the file to make sure that 'moov' is at the endwhile (readAndFill(infile, atomBytes)) {atomSize = uint32ToLong(atomBytes.getInt()); // uint32atomType = atomBytes.getInt(); // representing uint32_t in signed int// keep ftyp atomif (atomType == FTYP_ATOM) {int ftypAtomSize = uint32ToInt(atomSize); // XXX: assume in range of int32_tftypAtom = ByteBuffer.allocate(ftypAtomSize).order(ByteOrder.BIG_ENDIAN);atomBytes.rewind();ftypAtom.put(atomBytes);if (infile.read(ftypAtom) < ftypAtomSize - ATOM_PREAMBLE_SIZE) break;ftypAtom.flip();startOffset = infile.position(); // after ftyp atomSystem.out.println("QtFastStart---FTYP_ATOM---atomType:"+atomType);System.out.println("QtFastStart---FTYP_ATOM---atomType:"+String.valueOf(infile.position()));} else {//                System.out.println("QtFastStart------atomType:"+atomType);if (atomSize == 1) {/* 64-bit special case */atomBytes.clear();if (!readAndFill(infile, atomBytes)) break;atomSize = uint64ToLong(atomBytes.getLong()); // XXX: assume in range of int64_tinfile.position(infile.position() + atomSize - ATOM_PREAMBLE_SIZE * 2); // seekSystem.out.println("QtFastStart--atomSize == 1----atomType:"+atomType);} else {infile.position(infile.position() + atomSize - ATOM_PREAMBLE_SIZE); // seekSystem.out.println("QtFastStart--else----atomType:"+atomType);System.out.println("QtFastStart--else----atomType:"+String.valueOf(infile.position() + atomSize - ATOM_PREAMBLE_SIZE));}}if (sDEBUG) printf("%c%c%c%c %10d %d",(atomType >> 24) & 255,(atomType >> 16) & 255,(atomType >> 8) & 255,(atomType >> 0) & 255,infile.position() - atomSize,atomSize);if ((atomType != FREE_ATOM)&& (atomType != JUNK_ATOM)&& (atomType != MDAT_ATOM)&& (atomType != MOOV_ATOM)&& (atomType != PNOT_ATOM)&& (atomType != SKIP_ATOM)&& (atomType != WIDE_ATOM)&& (atomType != PICT_ATOM)&& (atomType != UUID_ATOM)&& (atomType != FTYP_ATOM)) {printf("encountered non-QT top-level atom (is this a QuickTime file?)");break;}/* The atom header is 8 (or 16 bytes), if the atom size (which* includes these 8 or 16 bytes) is less than that, we won't be* able to continue scanning sensibly after this atom, so break. */if (atomSize < 8)break;}System.out.println("QtFastStart------"+"第一循环结束atomType:"+atomType);if (atomType != MOOV_ATOM) {printf("last atom in file was not a moov atom");return false;}// moov atom was, in fact, the last atom in the chunk; load the whole moov atom// atomSize is uint64, but for moov uint32 should be stored.// XXX: assuming moov atomSize <= max vaue of int32moovAtomSize = uint32ToInt(atomSize);lastOffset = infile.size() - moovAtomSize; // NOTE: assuming no extra data after moov, as qt-faststart.cmoovAtom = ByteBuffer.allocate(moovAtomSize).order(ByteOrder.BIG_ENDIAN);if (!readAndFill(infile, moovAtom, lastOffset)) {throw new MalformedFileException("failed to read moov atom");}// this utility does not support compressed atoms yet, so disqualify files with compressed QT atomsif (moovAtom.getInt(12) == CMOV_ATOM) {throw new UnsupportedFileException("this utility does not support compressed moov atoms yet");}// crawl through the moov chunk in search of stco or co64 atomswhile (moovAtom.remaining() >= 8) {int atomHead = moovAtom.position();atomType = moovAtom.getInt(atomHead + 4); // representing uint32_t in signed intif (!(atomType == STCO_ATOM || atomType == CO64_ATOM)) {moovAtom.position(moovAtom.position() + 1);continue;}atomSize = uint32ToLong(moovAtom.getInt(atomHead)); // uint32if (atomSize > moovAtom.remaining()) {throw new MalformedFileException("bad atom size");}moovAtom.position(atomHead + 12); // skip size (4 bytes), type (4 bytes), version (1 byte) and flags (3 bytes)if (moovAtom.remaining() < 4) {throw new MalformedFileException("malformed atom");}// uint32_t, but assuming moovAtomSize is in int32 range, so this will be in int32 rangeint offsetCount = uint32ToInt(moovAtom.getInt());if (atomType == STCO_ATOM) {printf("patching stco atom...");if (moovAtom.remaining() < offsetCount * 4) {throw new MalformedFileException("bad atom size/element count");}for (int i = 0; i < offsetCount; i++) {int currentOffset = moovAtom.getInt(moovAtom.position());int newOffset = currentOffset + moovAtomSize; // calculate uint32 in int, bitwise addition// current 0xffffffff => new 0x00000000 (actual >= 0x0000000100000000L)if (currentOffset < 0 && newOffset >= 0) {throw new UnsupportedFileException("This is bug in original qt-faststart.c: "+ "stco atom should be extended to co64 atom as new offset value overflows uint32, "+ "but is not implemented.");}moovAtom.putInt(newOffset);}} else if (atomType == CO64_ATOM) {printf("patching co64 atom...");if (moovAtom.remaining() < offsetCount * 8) {throw new MalformedFileException("bad atom size/element count");}for (int i = 0; i < offsetCount; i++) {long currentOffset = moovAtom.getLong(moovAtom.position());moovAtom.putLong(currentOffset + moovAtomSize); // calculate uint64 in long, bitwise addition}}}infile.position(startOffset); // seek after ftyp atomif (ftypAtom != null) {// dump the same ftyp atomprintf("writing ftyp atom...");ftypAtom.rewind();outfile.write(ftypAtom);}// dump the new moov atomprintf("writing moov atom...");moovAtom.rewind();outfile.write(moovAtom);// copy the remainder of the infile, from offset 0 -> (lastOffset - startOffset) - 1printf("copying rest of file...");infile.transferTo(startOffset, lastOffset - startOffset, outfile);System.out.println("QtFastStart------"+"处理完成");return true;}public static class QtFastStartException extends Exception {private QtFastStartException(String detailMessage) {super(detailMessage);}}public static class MalformedFileException extends QtFastStartException {private MalformedFileException(String detailMessage) {super(detailMessage);}}public static class UnsupportedFileException extends QtFastStartException {private UnsupportedFileException(String detailMessage) {super(detailMessage);}}public static void main(String[] args) {try {String path = "C:\\Users\\shenyy\\Desktop\\待转换视频\\1540710345332.mp4";InputStream in = new FileInputStream(path);FileOutputStream out = new FileOutputStream(path+"_");boolean success = QtFastStart.fastStart(in, out);} catch (IOException e) {e.printStackTrace();} catch (MalformedFileException e) {e.printStackTrace();} catch (UnsupportedFileException e) {e.printStackTrace();}}}

附:视频转换前后的模块顺序对比 1前。2后

1
换顺序之前

2
换顺序之后
很明显,同一个文件,模块顺序变了,使用flash播放,moov在前的明显秒播。

效果

在这里插入图片描述

附 mp4文件详细格式
mp4文件结构


http://www.ppmy.cn/news/131951.html

相关文章

mp4播放器下载android,MP4视频播放器最新版下载_MP4视频播放器正式版_MP4视频播放器9.3-华军软件园...

MP4视频播放器是用于播放视频文件的播放工具。MP4是一种网络流行的高清视频格式&#xff0c;同样以高清晰度为人所喜爱&#xff0c;并且几乎所有工具都能够支持MP4文件的播放。但是MP4播放器可不止能播放MP4文件&#xff0c;同时还能支持其它多种视频格式的播放&#xff0c;需要…

打印机驱动

samsung三星打印机驱动下载_samsung三星打印机驱动大全_打印驱动网 (printdrv.com)

常用电脑打印机配置

一. 佳博GP-1324D型号打印机 先安装对应型号的驱动程序-》选择USB端口安装-》下一步....即可。 相关配置截图如下&#xff1a;

打印机纸张放置图标图解--如何区别空白面待打印页怎么放置,双面手动打印如何放对纸的方向

打印机纸张放置图标 这个标识的意思就是将打印在纸张的哪一面上。 上图中红框中的图标意思是将打印在所放纸张的下面。如果要翻面打印的话&#xff0c;就是把已经打印好的那面放在上面&#xff0c;箭头的意思表示头朝外放。 上图中红框中的图标意思是将打印在所放纸张的上面。如…

兄弟打印机内存已满清零方法_兄弟打印机全部清零操作方法

《兄弟打印机全部清零操作方法》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《兄弟打印机全部清零操作方法(6页珍藏版)》请在人人文库网上搜索。 1、兄弟打印机全部清零操作方法弟打印机清零大全一、兄弟 4040CN、4050 彩打硒鼓清零方法兄弟4040CN彩色激光打印机更换…

3D打印机 G代码解释

* -----------------**“G”代码** G0 -> G1* G1 - 协调运动 X Y Z E* G2 - 顺时针弧* G3 - 逆时针弧* G4 - 停留 S<秒> 或 P<毫秒>* G5 - 具有 XYZE 目标和 IJPQ 偏移的三次 B 样条* G10 - 根据 M207 的设置收回料丝&#xff08;需要 FWRETRACT&#xff09;* G…

xp系统打印服务器自动关闭,xp打印机服务器设置

Windows Server入门系列41 网络打印机的设置与使用 首先要在打印机服务器处为一台打印机安装多次驱动程序&#xff0c;生成多个不同的逻辑打印机&#xff0c;并分别为之设置不同的优先级。在虚拟机2003里再添加一台逻辑打印机printer2&#xff0c;操作过程同前面的一样。安装完…

兄弟j220怎么清零_兄弟j220怎么清零_兄弟Brother全系列打印机清零大全

一、兄弟 4040/4050 彩打硒鼓清零方法 兄弟 4040CN 彩色激光打印机更换硒鼓后清零方法如下&#xff1a; 当打印机出现“ Drum End Soon ”等字母提示时&#xff0c;就提醒您要更换硒鼓了。 当您更换新的硒鼓单元时&#xff0c;您需要通过以下步骤重置硒鼓计数器&#xff1a; 1 …