importcom.ctsi.common.utils.UUIDUtils;importorg.apache.commons.fileupload.FileItem;importorg.apache.commons.fileupload.disk.DiskFileItemFactory;importorg.apache.tomcat.util.http.fileupload.IOUtils;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.web.multipart.MultipartFile;importorg.springframework.web.multipart.commons.CommonsMultipartFile;importjava.io.*;importjava.nio.ByteBuffer;importjava.nio.ByteOrder;importjava.nio.channels.FileChannel;/**
* mp4源信息(moov)前置
*/publicclassQtFastStart{/* top level atoms */privatestaticfinalint FREE_ATOM =fourCcToInt(newbyte[]{'f','r','e','e'});privatestaticfinalint JUNK_ATOM =fourCcToInt(newbyte[]{'j','u','n','k'});privatestaticfinalint MDAT_ATOM =fourCcToInt(newbyte[]{'m','d','a','t'});privatestaticfinalint MOOV_ATOM =fourCcToInt(newbyte[]{'m','o','o','v'});privatestaticfinalint PNOT_ATOM =fourCcToInt(newbyte[]{'p','n','o','t'});privatestaticfinalint SKIP_ATOM =fourCcToInt(newbyte[]{'s','k','i','p'});privatestaticfinalint WIDE_ATOM =fourCcToInt(newbyte[]{'w','i','d','e'});privatestaticfinalint PICT_ATOM =fourCcToInt(newbyte[]{'P','I','C','T'});privatestaticfinalint FTYP_ATOM =fourCcToInt(newbyte[]{'f','t','y','p'});privatestaticfinalint UUID_ATOM =fourCcToInt(newbyte[]{'u','u','i','d'});privatestaticfinalint CMOV_ATOM =fourCcToInt(newbyte[]{'c','m','o','v'});privatestaticfinalint STCO_ATOM =fourCcToInt(newbyte[]{'s','t','c','o'});privatestaticfinalint CO64_ATOM =fourCcToInt(newbyte[]{'c','o','6','4'});/**
* 原子大小
*/privatestaticfinalint ATOM_PREAMBLE_SIZE =8;/**
* 是否打印错误日志标记
*/publicstaticboolean sDEBUG =false;/**
* 视频转换临时存放地址
*/@Value("${file.voice}")privatestaticString voice;staticlonguint32ToLong(int int32){return int32 &0x00000000ffffffffL;}/**
* Ensures passed uint32 value in long can be represented as Java int.
*/staticintuint32ToInt(int uint32)throwsUnsupportedFileException{if(uint32 <0){thrownewUnsupportedFileException("uint32 value is too large");}return uint32;}/**
* Ensures passed uint32 value in long can be represented as Java int.
*/staticintuint32ToInt(long uint32)throwsUnsupportedFileException{if(uint32 >Integer.MAX_VALUE || uint32 <0){thrownewUnsupportedFileException("uint32 value is too large");}return(int) uint32;}/**
* Ensures passed uint64 value can be represented as Java long.
*/staticlonguint64ToLong(long uint64)throwsUnsupportedFileException{if(uint64 <0)thrownewUnsupportedFileException("uint64 value is too large");return uint64;}privatestaticintfourCcToInt(byte[] byteArray){returnByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).getInt();}privatestaticvoidprintf(String format,Object... args){if(sDEBUG)System.err.println("QtFastStart: "+String.format(format, args));}privatestaticvoidprinte(Throwable e,String format,Object... args){printf(format, args);if(sDEBUG) e.printStackTrace();}privatestaticbooleanreadAndFill(FileChannel infile,ByteBuffer buffer)throwsIOException{
buffer.clear();int size = infile.read(buffer);
buffer.flip();return size == buffer.capacity();}privatestaticbooleanreadAndFill(FileChannel infile,ByteBuffer buffer,long position)throwsIOException{
buffer.clear();int size = infile.read(buffer, position);
buffer.flip();return size == buffer.capacity();}publicstaticMultipartFilefastStart(MultipartFile multipartFile)throwsIOException,MalformedFileException,UnsupportedFileException{// debug日志打印标志 true:开启; 默认false: 不开启
sDEBUG =true;// 转换结果标志boolean ret =false;// 中转临时文件名String name =UUIDUtils.getUUID()+".mp4";// 创建视频中转临时文件File tempFile =newFile(voice + name);// 上传文件输入流FileInputStream inStream =null;// 临时文件输入流FileOutputStream outStream =null;// 上传文件FileChannelFileChannel infile =null;// 临时文件FileChannelFileChannel outfile =null;try{// 获取文件输入流
inStream =(FileInputStream) multipartFile.getInputStream();// 获取输入流的FileChannel
infile = inStream.getChannel();// 创建临时文件输出流
outStream =newFileOutputStream(tempFile);// 获取临时文件输出流的FileChannel
outfile = outStream.getChannel();// 转换开始
ret =fastStartImpl(infile, outfile);}catch(Exception e){
e.printStackTrace();}finally{// 判定转换结果if(!ret){//当转换不成功时(正常是因文件小),直接copy.
infile.transferTo(0, infile.size(), outfile);}// 释放资源safeClose(inStream);safeClose(outStream);}// 临时文件输入流InputStream input =null;// 获取源文件名String fileName = multipartFile.getOriginalFilename();// 创建FileItemFileItem fileItem =newDiskFileItemFactory().createItem("file","video/mp4",false, fileName);try{// 读取临时文件
input =newFileInputStream(tempFile);// 获取FileItem输出流OutputStream os = fileItem.getOutputStream();// 流复制IOUtils.copy(input, os);}catch(Exception e){thrownewIllegalArgumentException("Invalid file: "+ e, e);}finally{// 释放资源safeClose(input);// 临时文件删除
tempFile.delete();}// 根据fileItem创建MultipartFilereturnnewCommonsMultipartFile(fileItem);}/**
* moov前置转换开始
*
* @param infile 转换前源文件输入流
* @param outfile 转换后输出流
* @return true:转换成功; false:转换失败
* @throws IOException
* @throws MalformedFileException
* @throws UnsupportedFileException
*/privatestaticbooleanfastStartImpl(FileChannel infile,FileChannel outfile)throwsIOException,MalformedFileException,UnsupportedFileException{// 内存中创建atomBytes缓冲区// ByteOrder.BIG_ENDIAN(大字节序)设置字节序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());// uint32
atomType = atomBytes.getInt();// representing uint32_t in signed int// keep ftyp atomif(atomType == FTYP_ATOM){int ftypAtomSize =uint32ToInt(atomSize);// XXX: assume in range of int32_t
ftypAtom =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_t
infile.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");returnfalse;}// 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 int32
moovAtomSize =uint32ToInt(atomSize);
lastOffset = infile.size()- moovAtomSize;// NOTE: assuming no extra data after moov, as qt-faststart.c
moovAtom =ByteBuffer.allocate(moovAtomSize).order(ByteOrder.BIG_ENDIAN);if(!readAndFill(infile, moovAtom, lastOffset)){thrownewMalformedFileException("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){thrownewUnsupportedFileException("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()){thrownewMalformedFileException("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){thrownewMalformedFileException("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){thrownewMalformedFileException("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){thrownewUnsupportedFileException("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);}}elseif(atomType == CO64_ATOM){printf("patching co64 atom...");if(moovAtom.remaining()< offsetCount *8){thrownewMalformedFileException("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------"+"处理完成");returntrue;}publicstaticclassQtFastStartExceptionextendsException{privateQtFastStartException(String detailMessage){super(detailMessage);}}publicstaticclassMalformedFileExceptionextendsQtFastStartException{privateMalformedFileException(String detailMessage){super(detailMessage);}}publicstaticclassUnsupportedFileExceptionextendsQtFastStartException{privateUnsupportedFileException(String detailMessage){super(detailMessage);}}/**
* 关闭流
*
* @param closeable
*/privatestaticvoidsafeClose(Closeable closeable){if(closeable !=null){try{
closeable.close();}catch(IOException e){printe(e,"Failed to close file: ");}}}}