Android 因moov播放网络mp4失败的解决办法

来源:互联网 发布:linux gdb调试core文件 编辑:程序博客网 时间:2024/06/05 20:56

解决思路:
1、通过网址读取mp4流的关键字来判断ftyp、free、mdat、moov。新建文件destFile,然后:
a、下载ftyp的全部到newFile
b、下载moov全部到newFile
c、写mdat大小的空白数据到newFIle
d、等b和c都完成之后(因b和c这两步的先后不确定),再重新定位mp4流到mdat部分,下载56k(大小可以自行设定,这里我设的是56k)的数据到newFile,然后通知播放端开始播放视频(就用MediaPlayer.setDataSource(newfile)方法),后台下载端继续将最后部分mdat数据完全下载到newFile,即完成mp4数据的下载

代码如下

import java.io.BufferedInputStream;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.io.UnsupportedEncodingException;import java.net.HttpURLConnection;import java.net.URL;/** * Created by Administrator on 2017/11/16. */public class Mp4DownloadUtils {    /**     * 播放MP4消息     */    public static final int PLAYER_MP4_MSG = 0x1001;    /**     * 下载MP4完成     */    public static final int DOWNLOAD_MP4_COMPLETION = 0x1002;    /**     * 下载MP4失败     */    public static final int DOWNLOAD_MP4_FAIL = 0x1003;    /**     * 下载MP4进度     */    public static final int DOWNLOAD_MP4_LOADING_PROCESS = 0x1004;    /**     * 下载MP4文件     *     * @param url     * @param fileName     * @param handler     * @return     */    public static File downloadMp4File(final String url, final String fileName,                                       final Handler handler) {        final File mp4File = new File(fileName);        downloadVideoToFile(url, mp4File, handler);        return mp4File;    }    /**     * 下载视频数据到文件     *     * @param url     * @param dstFile     */    private static final int BUFFER_SIZE = 4 * 1024;    static boolean isRunning = true;    static Thread thread;    public static void kill(){        if (thread!=null){            isRunning = false;            thread.interrupt();        }    }    /**     * @param url:mp4文件url     * @param dstFile:下载后缓存文件     * @param handler: 通知UI主线程的handler     */    private static void downloadVideoToFile(final String url, final File dstFile, final Handler handler) {         isRunning = true;         thread = new Thread() {             InputStream is = null;             RandomAccessFile raf = null;             BufferedInputStream bis = null;            @Override            public void run() {                super.run();                try {                    URL request = new URL(url);                    HttpURLConnection httpConn = (HttpURLConnection) request.openConnection();                    httpConn.setConnectTimeout(3000);                    httpConn.setDefaultUseCaches(false);                    httpConn.setRequestMethod("GET");//                    httpConn.setRequestProperty("Charset", "UTF-8");//                    httpConn.setRequestProperty("Accept-Encoding", "identity");                    int responseCode = httpConn.getResponseCode();                    Log.d("chy", responseCode + "");                    if ((responseCode == HttpURLConnection.HTTP_OK)) {//链接成功                        // 获取文件总长度//                        int totalLength = httpConn.getContentLength();                        is = httpConn.getInputStream();                        if (dstFile.exists()) {                            dstFile.delete();                        }                        //新建缓存文件                        dstFile.createNewFile();                        raf = new RandomAccessFile(dstFile, "rw");                        bis = new BufferedInputStream(is);                        int readSize;                        //读取Mp4流                        int mdatSize = 0;// mp4的mdat长度                        int headSize = 0;// mp4从流里已经读取的长度                        int mdatMark = 0;//mdat的标记位置                        byte[] boxSizeBuf = new byte[4];                        byte[] boxTypeBuf = new byte[4];                        // 由MP4的文件格式读取                        int boxSize = readBoxSize(bis, boxSizeBuf);                        String boxType = readBoxType(bis, boxTypeBuf);                        raf.write(boxSizeBuf);                        raf.write(boxTypeBuf);                        boolean isMoovRead = false;                        boolean isMdatRead = false;                        boolean isftypRead = false;                        byte[] buffer = new byte[BUFFER_SIZE];                        //判断ftyp、free、mdat、moov 并下载                        while (isRunning &&!Thread.currentThread().isInterrupted()) {                            int count = boxSize - 8;                            if (boxType.equalsIgnoreCase("ftyp")) {                                headSize += boxSize;                                byte[] ftyps = new byte[count];                                bis.read(ftyps, 0, count);                                raf.write(ftyps, 0, count);                                isftypRead = true;                                LogUtils.getInstance().i("ftyp ok");                            } else if (boxType.equalsIgnoreCase("mdat")) {                                headSize += boxSize;                                //正常模式                                mdatSize = boxSize - 8;                                int dealSize = mdatSize;                                //填充mdata大小的空白数据到dstFile,先填充destFile的大小                                while (dealSize > 0) {                                    if (dealSize > BUFFER_SIZE)                                        raf.write(buffer);                                    else                                        raf.write(buffer, 0, dealSize);                                    dealSize -= BUFFER_SIZE;                                }                                if(isMoovRead){//moov在mdat的前面,已经下载                                    //下载mdat到dest                                    downLoadMadta(bis,mdatSize,raf,headSize - boxSize+ 8,handler);                                    bis.close();                                    is.close();                                    raf.close();                                    httpConn.disconnect();                                    return;                                }else {//moov在mdat的后面                                    //下载moov到dest 与mp4服务器端重新连接一个通道,专门下载moov部分                                    downLoadMoov(url,headSize,raf);                                    //从destFile的起点开始,下载mdat到destFile                                    downLoadMadta(bis,mdatSize,raf,headSize - boxSize+ 8,handler);                                    bis.close();                                    is.close();                                    raf.close();                                    httpConn.disconnect();                                    return;                                }                            } else if (boxType.equalsIgnoreCase("free")) {                                headSize += boxSize;                            } else if (boxType.equalsIgnoreCase("moov")) {                                Log.d("chy","moov size:"+boxSize);                                headSize += boxSize;                                int moovSize = count;                                while (moovSize > 0) {                                    if (moovSize > BUFFER_SIZE) {                                        readSize = bis.read(buffer);                                    } else {                                        readSize = bis.read(buffer, 0, moovSize);                                    }                                    if (readSize == -1) break;                                    raf.write(buffer, 0, readSize);                                    moovSize -= readSize;                                }                                isMoovRead = true;                                LogUtils.getInstance().i("moov ok");                            }                            if (isftypRead && isMoovRead && isMdatRead) {                                break;                            }                            boxSize = readBoxSize(bis, boxSizeBuf);                            boxType = readBoxType(bis, boxTypeBuf);                            LogUtils.getInstance().i("boxSize:"+boxSize+" boxType:"+boxType);                            raf.write(boxSizeBuf);                            raf.write(boxTypeBuf);                        }                    }else {                        sendMessage(handler, DOWNLOAD_MP4_FAIL, null);                        is.close();                        bis.close();                        raf.close();                    }                } catch (Exception e) {                    e.printStackTrace();                    this.interrupt();  //中断这个分线程                    sendMessage(handler, DOWNLOAD_MP4_FAIL, null);                    try {                        if (is!=null && bis != null && raf != null){                            is.close();                            bis.close();                            raf.close();                        }                    } catch (IOException e1) {                        e1.printStackTrace();                    }                }            }        };        thread.start();//        thread = null;    }    /** 此函数只有在确定moov在mdat的后面才可以调用     * @param url:mp4服务器地址     * @param begin:mdat的末点,也就是moov的起点。     * */    private static void downLoadMoov(final String url, int begin, RandomAccessFile raf){        HttpURLConnection httpConn = null;        InputStream is = null;        BufferedInputStream bis = null;        try {            LogUtils.getInstance().i("downLoadMoov");            URL request = new URL(url);            httpConn = (HttpURLConnection) request.openConnection();            httpConn.setConnectTimeout(3000);            httpConn.setDefaultUseCaches(false);            httpConn.setRequestMethod("GET");            httpConn.setRequestProperty("range", "bytes=" + begin + "-");            is = httpConn.getInputStream();            bis = new BufferedInputStream(is);            int readSize =0;            int headSize = 0;// mp4从流里已经读取的长度            byte[] boxSizeBuf = new byte[4];            byte[] boxTypeBuf= new byte[4];            // 由MP4的文件格式读取            int boxSize = readBoxSize(bis, boxSizeBuf);            String boxType = readBoxType(bis, boxTypeBuf);            raf.write(boxSizeBuf);            raf.write(boxTypeBuf);            int count =0;            byte[] buffer = new byte[4*1024];            while(true) {                count = boxSize - 8;                if (boxType.equalsIgnoreCase("free")) {                    headSize += boxSize;                } else if (boxType.equalsIgnoreCase("moov")) {                    Log.d("chy", "moov size:" + boxSize);                    headSize += boxSize;                    int moovSize = count;                    while (moovSize > 0) {                        if (moovSize > BUFFER_SIZE) {                            readSize = bis.read(buffer);                        } else {                            readSize = bis.read(buffer, 0, moovSize);                        }                        if (readSize == -1) break;                        raf.write(buffer, 0, readSize);                        moovSize -= readSize;                    }                    LogUtils.getInstance().i("moov ok");                    break;                }                boxSize = readBoxSize(bis, boxSizeBuf);                boxType = readBoxType(bis, boxTypeBuf);                LogUtils.getInstance().i("boxSizeMoov:" + boxSize + " boxTypeMoov:" + boxType);                raf.write(boxSizeBuf);                raf.write(boxTypeBuf);            }            bis.close();            is.close();            httpConn.disconnect();        }catch (Exception e){            try {                bis.close();                is.close();            } catch (IOException e1) {                e1.printStackTrace();            }            if (httpConn!=null)            httpConn.disconnect();        }    }    /**     *  此方法直接定位到mdat的起点开始下载mdata。1、下载部分通知前端 2、下载完成也通知前端     *  @param bis:服务器流     *  @param mdatSize:mdat的大小     *  @param raf:目标文件的句柄     *  @param wirteSeek:让目标文件指针跳过ftyp等部分     * */    private static void downLoadMadta(BufferedInputStream bis,int mdatSize,RandomAccessFile raf,long wirteSeek,Handler handler) throws IOException {        LogUtils.getInstance().i("downLoadMadta");        final int buf_size = 56 * 1024;// 56kb        int downloadCount = 0;        boolean viable = false;        byte[] buffer = new byte[BUFFER_SIZE];        int readSize = 0;        if(wirteSeek>0){            raf.seek(wirteSeek);        }        int totalMdatSize = mdatSize;        while (mdatSize > 0) {            readSize = bis.read(buffer);            if(readSize < 0) break;            raf.write(buffer, 0, readSize);            mdatSize -= readSize;            downloadCount += readSize;            if (handler != null && !viable && downloadCount >= buf_size) {                LogUtils.getInstance().i("PLAYER_MP4_MSG");                viable = true;                // 发送开始播放视频消息,通知前台可以播放视频了                sendMessage(handler, PLAYER_MP4_MSG, null);            }            if (viable){                sendMessage(handler,DOWNLOAD_MP4_LOADING_PROCESS,1000L*downloadCount/totalMdatSize);            }        }        LogUtils.getInstance().i("下载完成");        // 发送下载消息 通知前台已经下载完成        if (handler != null) {            sendMessage(handler, DOWNLOAD_MP4_COMPLETION, null);            handler.removeMessages(PLAYER_MP4_MSG);            handler.removeMessages(DOWNLOAD_MP4_COMPLETION);        }    }    /**     * 发送下载消息     *     * @param handler     * @param what     * @param obj     */    private static void sendMessage(Handler handler, int what, Object obj) {        if (handler != null) {            Message msg = new Message();            msg.what = what;            msg.obj = obj;            handler.sendMessage(msg);        }    }    /**     * 跳转     *     * @param is     * @param count 跳转长度     * @throws IOException     */    private static void skip(BufferedInputStream is, long count) throws IOException {        while (count > 0) {            long amt = is.skip(count);            if (amt == -1) {                throw new RuntimeException("inputStream skip exception");            }            count -= amt;        }    }    /**     * 读取mp4文件box大小     *     * @param is     * @param buffer     * @return     */    private static int readBoxSize(InputStream is, byte[] buffer) {        int sz = fill(is, buffer);        if (sz == -1) {            return 0;        }        return bytesToInt(buffer, 0, 4);    }    /**     * 读取MP4文件box类型     *     * @param is     * @param buffer     * @return     */    private static String readBoxType(InputStream is, byte[] buffer) {        fill(is, buffer);        return byteToString(buffer);    }    /**     * byte转换int     *     * @param buffer     * @param pos     * @param bytes     * @return     */    private static int bytesToInt(byte[] buffer, int pos, int bytes) {        /*         * int intvalue = (buffer[pos + 0] & 0xFF) << 24 | (buffer[pos + 1] &         * 0xFF) << 16 | (buffer[pos + 2] & 0xFF) << 8 | buffer[pos + 3] & 0xFF;         */        int retval = 0;        for (int i = 0; i < bytes; ++i) {            retval |= (buffer[pos + i] & 0xFF) << (8 * (bytes - i - 1));        }        return retval;    }    /**     * byte数据转换String     *     * @param buffer     * @return     */    private static String byteToString(byte[] buffer) {        assert buffer.length == 4;        String retval = new String();        try {            retval = new String(buffer, 0, buffer.length, "ascii");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        return retval;    }    private static int fill(InputStream stream, byte[] buffer) {        return fill(stream, 0, buffer.length, buffer);    }    /**     * 读取流数据     *     * @param stream     * @param pos     * @param len     * @param buffer     * @return     */    private static int fill(InputStream stream, int pos, int len, byte[] buffer) {        int readSize = 0;        try {            readSize = stream.read(buffer, pos, len);            if (readSize == -1) {                return -1;            }            assert readSize == len : String.format("len %d readSize %d", len,                    readSize);        } catch (IOException e) {            e.printStackTrace();        }        return readSize;    }}

上述代码让moov无论是在mdat前面还是在moov的后面都可以播放。
作者:陈宇、茆文涛