JAVA通過URL鏈接獲取視頻文件信息(無需下載文件)


  最近項目碰到一個大坑:APP上需要在獲取視頻列表時就獲取視頻的時長,但早期上傳的時候數據庫都沒有保存這個數據,所以前段時間添加一個時長字段,在上傳時手動輸入視頻時長,但是之前庫中有上萬條數據沒這個信息,如果這樣一條一條手動輸入,人都得瘋掉。所以誰也不提不管這破事,在這之前的視頻時長信息就讓它空在那。最近領導讓我做個按類目分類統計視頻時長信息,和領導反映了這個問題,最終解決方案就把沒有的做0處理。在完成了這個功能后,我就在想能用什么方式把之前的視頻時長全部給更新上去。手動輸入這個肯定時不行的,必須得java后台來獲取錄入。但上網搜索了無數的帖子,最終通過java實現的只有一種方法能用,那就是先要下載到本地,然后再一個一個的遍歷查詢。看着服務器上的上萬個視頻,想想這方法就讓人頭皮發麻。

  雖然沒找到可行方法,但基本上都是用jave獲取視頻信息的。於是就去查看jave的官方API,了解到是通過FFmpeg處理多媒體文件,接着又查看FFmpeg的API,發現ffmpeg在命令行中使用時可以通過url獲取視頻。但使用jave工具包時獲取MultimediaInfo就必須得傳入File,可是又不能通過url創建File。於是就就反編譯jave的jar從源碼上動手。

// 源碼
public
MultimediaInfo getInfo(File source) throws InputFormatException, EncoderException { FFMPEGExecutor ffmpeg; ffmpeg = locator.createExecutor(); ffmpeg.addArgument("-i"); ffmpeg.addArgument(source.getAbsolutePath()); try { ffmpeg.execute(); } catch(IOException e) { throw new EncoderException(e); } MultimediaInfo multimediainfo; RBufferedReader reader = null; reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); multimediainfo = parseMultimediaInfo(source, reader); ffmpeg.destroy(); return multimediainfo; Exception exception; exception; ffmpeg.destroy(); throw exception; }

ffmpeg傳入參數時使用的是

source.getAbsolutePath()獲取文件的絕對路徑,所以通過url創建File在這是獲取的就是 項目路徑+url了。

然后就把傳入path修改成了url,但是運行還是出現 InputFormatException異常。好吧,那就繼續找問題吧

然后debug發現雖然修改了path,但是這路徑細看還是不對

http://v1.v.123.com\11\919\2019\zb\0181.mp4
正確的url應該是這樣的:http://v1.v.123.com/11/919/2019/zb/0181.mp4

 接着更正問題。

 if(path.indexOf("http") != -1) {
            path = source.getPath();
            path = path.split(":")[0] + "://" + path.split(":")[1].substring(1);
            path = path.replace("\\", "/");
        }

 

這次終於沒問題了,可以正常使用了。然后還有下面這個方法的調用,源碼中有個獲取異常信息的也得修改path值

multimediainfo = parseMultimediaInfo(source, reader);

 這個也和只需重復上面的操作就OK了。這樣就完全搞定了。

import lx.jave.AudioAttributes;
import lx.jave.AudioInfo;
import lx.jave.Encoder;
import lx.jave.EncoderException;
import lx.jave.EncodingAttributes;
import lx.jave.InputFormatException;
import lx.jave.MultimediaInfo;
import lx.jave.VideoInfo;
import lx.jave.VideoSize;

/**
 * jave多媒體工具類(需導出jave  jar包) 
 * @author longxiong
 *
 */
public class JaveToolsTest {

    public static void main(String[] args) throws InputFormatException, EncoderException, Exception {

        /**
         * 獲取本地多媒體文件信息
         */
        // 編碼器
        Encoder encoder = new Encoder();
        File file = new File("http://*****018.mp4");
        // 多媒體信息
        MultimediaInfo info = encoder.getInfo(file);
        // 時長信息
        long duration = info.getDuration();
        System.out.println("視頻時長為:" + duration / 1000 + "秒");
        // 音頻信息
        AudioInfo audio = info.getAudio();
        int bitRate = audio.getBitRate();  // 比特率
        int channels = audio.getChannels();  // 聲道
        String decoder = audio.getDecoder();  // 解碼器
        int sRate = audio.getSamplingRate();  // 采樣率
        System.out.println("解碼器:" + decoder + ",聲道:" + channels + ",比特率:" + bitRate + ",采樣率:" + sRate);
        // 視頻信息
        VideoInfo video = info.getVideo();
        int bitRate2 = video.getBitRate();
        Float fRate = video.getFrameRate();  // 幀率
        VideoSize videoSize = video.getSize();
        int height = videoSize.getHeight();  // 視頻高度
        int width = videoSize.getWidth();  // 視頻寬度
        System.out.println("視頻幀率:" + fRate + ",比特率:" + bitRate2 + ",視頻高度:" + height + ",視頻寬度:" + width);
        }
}

 

 

雖然是比較簡單的修改,還是附上修改后的jar包吧。

鏈接:https://pan.baidu.com/s/1gqsfl_2Tq2swbMY-mQUQeg
提取碼:zpdh

mac系統使用下面的jar包

鏈接:https://pan.baidu.com/s/12g9o7NgLtze7v2aSMaGadg

附帶測試一下讀取性能:

單線程讀取20個視頻:

多線程(開啟了10個線程)讀取20個視頻:

從數據上看采用多線程性能還是可以的。不過幾千上萬的數據就不知道會不會崩了。下次有空在測試一下。

一次讀取2000鏈接測試:

多線程的處理方法:

采用多線程讀取大量數據測試時,由於數據的寫入是等獲取完所有信息后一次寫入數據的,有時會因為ffmpeg.exe進程不能正常關閉,導致程序不能執行到最后。在這個問題上卡了一段時間,以本人目前所掌握的一點點知識,最終只能以下面方式處理這個問題。

 

@RequestMapping(value="/initVideoDuration")
    public void initVideoDuration(Video video, Integer page, Integer rows) throws InterruptedException {
        // 通過分頁處理數據
        Integer startRow = (page - 1) * rows;
        List<Video> videos = videoMapper.getVideoByPage(startRow, rows);
        Long start = System.currentTimeMillis();
        int corePoolSize = 10;
        int maxPoolSize = 10;
        long keepAliveTime = 15;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
        // 創建線程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
        pool.allowCoreThreadTimeOut(true);
        CountDownLatch countLatch = new CountDownLatch(videos.size());
        List<Video> list = new ArrayList<Video>();
        for(int i = 0; i < videos.size(); i++) {
            // 將任務加入線程池
            pool.execute(new ExecutorTask(countLatch, videos.get(i), list));
        }
        // 阻塞主線程
        countLatch.await();
        // 關閉線程池
        pool.shutdown();
        // 等待任務全部執行完畢
        // 由於CountDownLatch為0時任務還未執行完畢,這里通過判斷線程池已完成總任務數繼續對主進程進行阻塞
        while(pool.getCompletedTaskCount() != videos.size()) {        
        }
        // 當線程池關閉后強制結束所有未正常關閉的ffmpeg.exe進程
        String cmd = "taskkill /F /IM ffmpeg.exe";
        Runtime runtime = Runtime.getRuntime();
        try {
            runtime.exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("總完成任務數:" + pool.getCompletedTaskCount());
        System.out.println("---------");
        System.out.println("讀取" + videos.size() + "個鏈接,成功獲取" + list.size() + "個視頻,耗時:" + (end - start)/1000 + "秒");
        // 寫入數據庫
        videoMapper.addVideoDuration(list);
    }

/**
 * @author longxiong
 * 使用CountDownLatch計數器監視任務的執行情況。
 * 由於ffmpeg進程可能會出現沒能正常關閉,導致任務一直處於執行狀態,所以在開啟任務時就先將CountDownLatch減1,防止CountDownLatch值不能為0
 *
 */
class ExecutorTask implements Runnable {
    private CountDownLatch latch;
    private Video video;
    private List<Video> videoList;
    
    public ExecutorTask(CountDownLatch latch, Video video, List<Video> videoList) {
        this.latch = latch;
        this.video = video;
        this.videoList = videoList;
    }
    @Override
    public void run() {
        Encoder encoder = new Encoder();
        String url = video.getVideoUrl();      
        File file = new File(url);
        try {
            // 計數器減1
            latch.countDown();
            MultimediaInfo info = encoder.getInfo(file);
            long duration = info.getDuration()/1000;
            String time = "";
            // 獲取duration為總秒數,格式化為HH:mm:ss
            if(duration != 0) {
                if(duration/3600 != 0) {
                    time += duration/3600 + ":";
                }
                if(duration/60 != 0) {
                    if((duration%3600)/60 < 10) {
                        time += "0";
                    }
                    time += (duration%3600)/60 + ":";
                } else {
                    time += "00:";
                }
                if(duration%60 < 10) {
                    time += "0";
                }
                time += duration%60;
            }
            
            Video v = new Video();
            v.setId(video.getId());
            v.setDuration(time);
            videoList.add(v);
            System.out.println("視頻ID:" + video.getId() + ",時長:" + time + ",總共:" + duration + "秒");
        } catch(Exception e) {        
            System.out.println("視頻" + video.getId() + "獲取時長失敗");
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM